postgresql/src/fe_utils/print.c

3660 lines
84 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* Query-result printing support for frontend code
*
* This file used to be part of psql, but now it's separated out to allow
* other frontend programs to use it. Because the printing code needs
* access to the cancel_pressed flag as well as SIGPIPE trapping and
* pager open/close functions, all that stuff came with it.
*
*
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
2000-01-19 00:30:24 +01:00
*
* src/fe_utils/print.c
2000-01-19 00:30:24 +01:00
*
*-------------------------------------------------------------------------
2000-01-19 00:30:24 +01:00
*/
#include "postgres_fe.h"
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <unistd.h>
#ifndef WIN32
#include <sys/ioctl.h> /* for ioctl() */
#endif
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#include "catalog/pg_type_d.h"
#include "fe_utils/mbprint.h"
#include "fe_utils/print.h"
/*
* If the calling program doesn't have any mechanism for setting
* cancel_pressed, it will have no effect.
*
* Note: print.c's general strategy for when to check cancel_pressed is to do
* so at completion of each row of output.
*/
volatile bool cancel_pressed = false;
static bool always_ignore_sigpipe = false;
/* info for locale-aware numeric formatting; set up by setDecimalLocale() */
static char *decimal_point;
static int groupdigits;
static char *thousands_sep;
static char default_footer[100];
static printTableFooter default_footer_cell = {default_footer, NULL};
/* Line style control structures */
const printTextFormat pg_asciiformat =
{
"ascii",
{
2010-02-26 03:01:40 +01:00
{"-", "+", "+", "+"},
{"-", "+", "+", "+"},
{"-", "+", "+", "+"},
{"", "|", "|", "|"}
},
"|",
"|",
"|",
" ",
"+",
" ",
"+",
".",
".",
true
};
const printTextFormat pg_asciiformat_old =
{
"old-ascii",
{
2010-02-26 03:01:40 +01:00
{"-", "+", "+", "+"},
{"-", "+", "+", "+"},
{"-", "+", "+", "+"},
{"", "|", "|", "|"}
},
":",
";",
" ",
"+",
" ",
" ",
" ",
" ",
" ",
false
};
/* Default unicode linestyle format */
printTextFormat pg_utf8format;
2015-05-24 03:35:49 +02:00
typedef struct unicodeStyleRowFormat
{
const char *horizontal;
const char *vertical_and_right[2];
const char *vertical_and_left[2];
} unicodeStyleRowFormat;
2015-05-24 03:35:49 +02:00
typedef struct unicodeStyleColumnFormat
{
const char *vertical;
const char *vertical_and_horizontal[2];
const char *up_and_horizontal[2];
const char *down_and_horizontal[2];
} unicodeStyleColumnFormat;
2015-05-24 03:35:49 +02:00
typedef struct unicodeStyleBorderFormat
{
const char *up_and_right;
const char *vertical;
const char *down_and_right;
const char *horizontal;
const char *down_and_left;
const char *left_and_right;
} unicodeStyleBorderFormat;
2015-05-24 03:35:49 +02:00
typedef struct unicodeStyleFormat
{
unicodeStyleRowFormat row_style[2];
unicodeStyleColumnFormat column_style[2];
unicodeStyleBorderFormat border_style[2];
const char *header_nl_left;
const char *header_nl_right;
const char *nl_left;
const char *nl_right;
const char *wrap_left;
const char *wrap_right;
2015-05-24 03:35:49 +02:00
bool wrap_right_border;
} unicodeStyleFormat;
static const unicodeStyleFormat unicode_style = {
{
{
/* ─ */
"\342\224\200",
/* ├╟ */
{"\342\224\234", "\342\225\237"},
/* ┤╢ */
{"\342\224\244", "\342\225\242"},
},
{
/* ═ */
"\342\225\220",
/* ╞╠ */
{"\342\225\236", "\342\225\240"},
/* ╡╣ */
{"\342\225\241", "\342\225\243"},
},
},
{
{
/* │ */
"\342\224\202",
/* ┼╪ */
{"\342\224\274", "\342\225\252"},
/* ┴╧ */
{"\342\224\264", "\342\225\247"},
/* ┬╤ */
{"\342\224\254", "\342\225\244"},
},
{
/* ║ */
"\342\225\221",
/* ╫╬ */
{"\342\225\253", "\342\225\254"},
/* ╨╩ */
{"\342\225\250", "\342\225\251"},
/* ╥╦ */
{"\342\225\245", "\342\225\246"},
},
},
{
/* └│┌─┐┘ */
{"\342\224\224", "\342\224\202", "\342\224\214", "\342\224\200", "\342\224\220", "\342\224\230"},
/* ╚║╔═╗╝ */
{"\342\225\232", "\342\225\221", "\342\225\224", "\342\225\220", "\342\225\227", "\342\225\235"},
},
" ",
2015-05-24 03:35:49 +02:00
"\342\206\265", /* ↵ */
" ",
2015-05-24 03:35:49 +02:00
"\342\206\265", /* ↵ */
"\342\200\246", /* … */
"\342\200\246", /* … */
true
};
/* Local functions */
static int strlen_max_width(unsigned char *str, int *target_width, int encoding);
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
static void IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
FILE **fout, bool *is_pager);
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
static void print_aligned_vertical(const printTableContent *cont,
FILE *fout, bool is_pager);
/* Count number of digits in integral part of number */
static int
integer_digits(const char *my_str)
{
/* ignoring any sign ... */
if (my_str[0] == '-' || my_str[0] == '+')
my_str++;
/* ... count initial integral digits */
return strspn(my_str, "0123456789");
}
/* Compute additional length required for locale-aware numeric output */
static int
2005-07-18 22:57:53 +02:00
additional_numeric_locale_len(const char *my_str)
{
2005-10-15 04:49:52 +02:00
int int_len = integer_digits(my_str),
len = 0;
/* Account for added thousands_sep instances */
if (int_len > groupdigits)
len += ((int_len - 1) / groupdigits) * strlen(thousands_sep);
/* Account for possible additional length of decimal_point */
if (strchr(my_str, '.') != NULL)
len += strlen(decimal_point) - 1;
2005-10-15 04:49:52 +02:00
return len;
}
/*
* Format a numeric value per current LC_NUMERIC locale setting
*
* Returns the appropriately formatted string in a new allocated block,
* caller must free.
*
* setDecimalLocale() must have been called earlier.
*/
static char *
format_numeric_locale(const char *my_str)
{
char *new_str;
int new_len,
int_len,
leading_digits,
i,
new_str_pos;
/*
* If the string doesn't look like a number, return it unchanged. This
* check is essential to avoid mangling already-localized "money" values.
*/
if (strspn(my_str, "0123456789+-.eE") != strlen(my_str))
return pg_strdup(my_str);
new_len = strlen(my_str) + additional_numeric_locale_len(my_str);
new_str = pg_malloc(new_len + 1);
new_str_pos = 0;
int_len = integer_digits(my_str);
/* number of digits in first thousands group */
leading_digits = int_len % groupdigits;
if (leading_digits == 0)
leading_digits = groupdigits;
/* process sign */
if (my_str[0] == '-' || my_str[0] == '+')
{
new_str[new_str_pos++] = my_str[0];
my_str++;
}
/* process integer part of number */
for (i = 0; i < int_len; i++)
{
/* Time to insert separator? */
if (i > 0 && --leading_digits == 0)
{
strcpy(&new_str[new_str_pos], thousands_sep);
new_str_pos += strlen(thousands_sep);
leading_digits = groupdigits;
}
new_str[new_str_pos++] = my_str[i];
}
/* handle decimal point if any */
if (my_str[i] == '.')
{
strcpy(&new_str[new_str_pos], decimal_point);
new_str_pos += strlen(decimal_point);
i++;
}
2005-10-15 04:49:52 +02:00
/* copy the rest (fractional digits and/or exponent, and \0 terminator) */
strcpy(&new_str[new_str_pos], &my_str[i]);
/* assert we didn't underestimate new_len (an overestimate is OK) */
Assert(strlen(new_str) <= new_len);
2005-10-15 04:49:52 +02:00
return new_str;
}
/*
* fputnbytes: print exactly N bytes to a file
*
* We avoid using %.*s here because it can misbehave if the data
* is not valid in what libc thinks is the prevailing encoding.
*/
static void
fputnbytes(FILE *f, const char *str, size_t n)
{
while (n-- > 0)
fputc(*str++, f);
}
static void
print_separator(struct separator sep, FILE *fout)
{
if (sep.separator_zero)
fputc('\000', fout);
else if (sep.separator)
fputs(sep.separator, fout);
}
/*
* Return the list of explicitly-requested footers or, when applicable, the
* default "(xx rows)" footer. Always omit the default footer when given
* non-default footers, "\pset footer off", or a specific instruction to that
* effect from a calling backslash command. Vertical formats number each row,
* making the default footer redundant; they do not call this function.
*
* The return value may point to static storage; do not keep it across calls.
*/
static printTableFooter *
footers_with_default(const printTableContent *cont)
{
if (cont->footers == NULL && cont->opt->default_footer)
{
unsigned long total_records;
total_records = cont->opt->prior_records + cont->nrows;
snprintf(default_footer, sizeof(default_footer),
ngettext("(%lu row)", "(%lu rows)", total_records),
total_records);
return &default_footer_cell;
}
else
return cont->footers;
}
/*************************/
1999-11-05 00:14:30 +01:00
/* Unaligned text */
/*************************/
static void
print_unaligned_text(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
1999-11-05 00:14:30 +01:00
unsigned int i;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
bool need_recordsep = false;
1999-11-05 00:14:30 +01:00
if (cancel_pressed)
return;
if (cont->opt->start_table)
{
/* print title */
if (!opt_tuples_only && cont->title)
{
fputs(cont->title, fout);
print_separator(cont->opt->recordSep, fout);
}
/* print headers */
if (!opt_tuples_only)
1999-11-05 00:14:30 +01:00
{
for (ptr = cont->headers; *ptr; ptr++)
{
if (ptr != cont->headers)
print_separator(cont->opt->fieldSep, fout);
fputs(*ptr, fout);
}
need_recordsep = true;
1999-11-05 00:14:30 +01:00
}
}
2006-10-04 02:30:14 +02:00
else
/* assume continuing printout */
need_recordsep = true;
1999-11-05 00:14:30 +01:00
/* print cells */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
1999-11-05 00:14:30 +01:00
{
if (need_recordsep)
{
print_separator(cont->opt->recordSep, fout);
need_recordsep = false;
if (cancel_pressed)
break;
}
fputs(*ptr, fout);
2005-10-15 04:49:52 +02:00
if ((i + 1) % cont->ncolumns)
print_separator(cont->opt->fieldSep, fout);
1999-11-05 00:14:30 +01:00
else
need_recordsep = true;
1999-11-05 00:14:30 +01:00
}
1999-11-05 00:14:30 +01:00
/* print footers */
if (cont->opt->stop_table)
{
printTableFooter *footers = footers_with_default(cont);
if (!opt_tuples_only && footers != NULL && !cancel_pressed)
{
printTableFooter *f;
for (f = footers; f; f = f->next)
{
if (need_recordsep)
{
print_separator(cont->opt->recordSep, fout);
need_recordsep = false;
}
fputs(f->data, fout);
need_recordsep = true;
}
}
/*
* The last record is terminated by a newline, independent of the set
* record separator. But when the record separator is a zero byte, we
* use that (compatible with find -print0 and xargs).
*/
if (need_recordsep)
{
if (cont->opt->recordSep.separator_zero)
print_separator(cont->opt->recordSep, fout);
else
fputc('\n', fout);
}
}
}
static void
print_unaligned_vertical(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
1999-11-05 00:14:30 +01:00
unsigned int i;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
bool need_recordsep = false;
1999-11-05 00:14:30 +01:00
if (cancel_pressed)
return;
if (cont->opt->start_table)
{
/* print title */
if (!opt_tuples_only && cont->title)
{
fputs(cont->title, fout);
need_recordsep = true;
}
}
2006-10-04 02:30:14 +02:00
else
/* assume continuing printout */
need_recordsep = true;
1999-11-05 00:14:30 +01:00
/* print records */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
1999-11-05 00:14:30 +01:00
{
if (need_recordsep)
{
/* record separator is 2 occurrences of recordsep in this mode */
print_separator(cont->opt->recordSep, fout);
print_separator(cont->opt->recordSep, fout);
need_recordsep = false;
if (cancel_pressed)
break;
}
fputs(cont->headers[i % cont->ncolumns], fout);
print_separator(cont->opt->fieldSep, fout);
fputs(*ptr, fout);
if ((i + 1) % cont->ncolumns)
print_separator(cont->opt->recordSep, fout);
else
need_recordsep = true;
}
if (cont->opt->stop_table)
1999-11-05 00:14:30 +01:00
{
/* print footers */
if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
{
printTableFooter *f;
print_separator(cont->opt->recordSep, fout);
for (f = cont->footers; f; f = f->next)
{
print_separator(cont->opt->recordSep, fout);
fputs(f->data, fout);
}
}
2000-01-19 00:30:24 +01:00
/* see above in print_unaligned_text() */
if (need_recordsep)
{
if (cont->opt->recordSep.separator_zero)
print_separator(cont->opt->recordSep, fout);
else
fputc('\n', fout);
}
}
}
/********************/
1999-11-05 00:14:30 +01:00
/* Aligned text */
/********************/
/* draw "line" */
static void
_print_horizontal_line(const unsigned int ncolumns, const unsigned int *widths,
unsigned short border, printTextRule pos,
const printTextFormat *format,
FILE *fout)
{
const printTextLineFormat *lformat = &format->lrule[pos];
1999-11-05 00:14:30 +01:00
unsigned int i,
j;
if (border == 1)
fputs(lformat->hrule, fout);
1999-11-05 00:14:30 +01:00
else if (border == 2)
fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule);
1999-11-05 00:14:30 +01:00
for (i = 0; i < ncolumns; i++)
1999-11-05 00:14:30 +01:00
{
for (j = 0; j < widths[i]; j++)
fputs(lformat->hrule, fout);
1999-11-05 00:14:30 +01:00
if (i < ncolumns - 1)
1999-11-05 00:14:30 +01:00
{
if (border == 0)
fputc(' ', fout);
else
fprintf(fout, "%s%s%s", lformat->hrule,
lformat->midvrule, lformat->hrule);
1999-11-05 00:14:30 +01:00
}
}
if (border == 2)
fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule);
1999-11-05 00:14:30 +01:00
else if (border == 1)
fputs(lformat->hrule, fout);
1999-11-05 00:14:30 +01:00
fputc('\n', fout);
}
/*
* Print pretty boxes around cells.
*/
static void
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
{
bool opt_tuples_only = cont->opt->tuples_only;
int encoding = cont->opt->encoding;
unsigned short opt_border = cont->opt->border;
const printTextFormat *format = get_line_style(cont->opt);
const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA];
unsigned int col_count = 0,
cell_count = 0;
unsigned int i,
j;
unsigned int *width_header,
*max_width,
*width_wrap,
*width_average;
unsigned int *max_nl_lines, /* value split by newlines */
*curr_nl_line,
*max_bytes;
unsigned char **format_buf;
unsigned int width_total;
unsigned int total_header_width;
unsigned int extra_row_output_lines = 0;
unsigned int extra_output_lines = 0;
2006-10-04 02:30:14 +02:00
2017-06-21 20:39:04 +02:00
const char *const *ptr;
2006-10-04 02:30:14 +02:00
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
struct lineptr **col_lineptrs; /* pointers to line pointer per column */
2006-10-04 02:30:14 +02:00
bool *header_done; /* Have all header lines been output? */
int *bytes_output; /* Bytes output for column value */
printTextLineWrap *wrap; /* Wrap status for each column */
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
int output_columns = 0; /* Width of interactive console */
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
bool is_local_pager = false;
1999-11-05 00:14:30 +01:00
if (cancel_pressed)
return;
if (opt_border > 2)
opt_border = 2;
if (cont->ncolumns > 0)
1999-11-05 00:14:30 +01:00
{
col_count = cont->ncolumns;
width_header = pg_malloc0(col_count * sizeof(*width_header));
width_average = pg_malloc0(col_count * sizeof(*width_average));
max_width = pg_malloc0(col_count * sizeof(*max_width));
width_wrap = pg_malloc0(col_count * sizeof(*width_wrap));
max_nl_lines = pg_malloc0(col_count * sizeof(*max_nl_lines));
curr_nl_line = pg_malloc0(col_count * sizeof(*curr_nl_line));
col_lineptrs = pg_malloc0(col_count * sizeof(*col_lineptrs));
max_bytes = pg_malloc0(col_count * sizeof(*max_bytes));
format_buf = pg_malloc0(col_count * sizeof(*format_buf));
header_done = pg_malloc0(col_count * sizeof(*header_done));
bytes_output = pg_malloc0(col_count * sizeof(*bytes_output));
wrap = pg_malloc0(col_count * sizeof(*wrap));
}
else
{
width_header = NULL;
width_average = NULL;
max_width = NULL;
width_wrap = NULL;
max_nl_lines = NULL;
curr_nl_line = NULL;
col_lineptrs = NULL;
max_bytes = NULL;
format_buf = NULL;
header_done = NULL;
bytes_output = NULL;
wrap = NULL;
Commit Patrice's patches except: > - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 > characters (characters with values >= 0x10000, which are encoded on > four bytes). Also, update mb/expected/unicode.out. This is necessary since the patches affetc the result of queries using UTF-8. --------------------------------------------------------------- Hi, I should have sent the patch earlier, but got delayed by other stuff. Anyway, here is the patch: - most of the functionality is only activated when MULTIBYTE is defined, - check valid UTF-8 characters, client-side only yet, and only on output, you still can send invalid UTF-8 to the server (so, it's only partly compliant to Unicode 3.1, but that's better than nothing). - formats with the correct number of columns (that's why I made it in the first place after all), but only for UNICODE. However, the code allows to plug-in routines for other encodings, as Tatsuo did for the other multibyte functions. - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 characters (characters with values >= 0x10000, which are encoded on four bytes). - doesn't depend on the locale capabilities of the glibc (useful for remote telnet). I would like somebody to check it closely, as it is my first patch to pgsql. Also, I created dummy .orig files, so that the two files I created are included, I hope that's the right way. Now, a lot of functionality is NOT included here, but I will keep that for 7.3 :) That includes all string checking on the server side (which will have to be a bit more optimised ;) ), and the input checking on the client side for UTF-8, though that should not be difficult. It's just to send the strings through mbvalidate() before sending them to the server. Strong checking on UTF-8 strings is mandatory to be compliant with Unicode 3.1+ . Do I have time to look for a patch to include iso-8859-15 for 7.2 ? The euro is coming 1. january 2002 (before 7.3 !) and over 280 millions people in Europe will need the euro sign and only iso-8859-15 and iso-8859-16 have it (and unfortunately, I don't think all Unices will switch to Unicode in the meantime).... err... yes, I know that this is not every single person in Europe that uses PostgreSql, so it's not exactly 280m, but it's just a matter of time ! ;) I'll come back (on pgsql-hackers) later to ask a few questions regarding the full unicode support (normalisation, collation, regexes,...) on the server side :) Here is the patch ! Patrice. -- Patrice HÉDÉ ------------------------------- patrice à islande org ----- -- Isn't it weird how scientists can imagine all the matter of the universe exploding out of a dot smaller than the head of a pin, but they can't come up with a more evocative name for it than "The Big Bang" ? -- What would _you_ call the creation of the universe ? -- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes ------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
}
2006-10-04 02:30:14 +02:00
/* scan all column headers, find maximum width and max max_nl_lines */
for (i = 0; i < col_count; i++)
{
int width,
nl_lines,
bytes_required;
pg_wcssize((const unsigned char *) cont->headers[i], strlen(cont->headers[i]),
encoding, &width, &nl_lines, &bytes_required);
if (width > max_width[i])
max_width[i] = width;
if (nl_lines > max_nl_lines[i])
max_nl_lines[i] = nl_lines;
if (bytes_required > max_bytes[i])
max_bytes[i] = bytes_required;
if (nl_lines > extra_row_output_lines)
extra_row_output_lines = nl_lines;
width_header[i] = width;
Commit Patrice's patches except: > - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 > characters (characters with values >= 0x10000, which are encoded on > four bytes). Also, update mb/expected/unicode.out. This is necessary since the patches affetc the result of queries using UTF-8. --------------------------------------------------------------- Hi, I should have sent the patch earlier, but got delayed by other stuff. Anyway, here is the patch: - most of the functionality is only activated when MULTIBYTE is defined, - check valid UTF-8 characters, client-side only yet, and only on output, you still can send invalid UTF-8 to the server (so, it's only partly compliant to Unicode 3.1, but that's better than nothing). - formats with the correct number of columns (that's why I made it in the first place after all), but only for UNICODE. However, the code allows to plug-in routines for other encodings, as Tatsuo did for the other multibyte functions. - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 characters (characters with values >= 0x10000, which are encoded on four bytes). - doesn't depend on the locale capabilities of the glibc (useful for remote telnet). I would like somebody to check it closely, as it is my first patch to pgsql. Also, I created dummy .orig files, so that the two files I created are included, I hope that's the right way. Now, a lot of functionality is NOT included here, but I will keep that for 7.3 :) That includes all string checking on the server side (which will have to be a bit more optimised ;) ), and the input checking on the client side for UTF-8, though that should not be difficult. It's just to send the strings through mbvalidate() before sending them to the server. Strong checking on UTF-8 strings is mandatory to be compliant with Unicode 3.1+ . Do I have time to look for a patch to include iso-8859-15 for 7.2 ? The euro is coming 1. january 2002 (before 7.3 !) and over 280 millions people in Europe will need the euro sign and only iso-8859-15 and iso-8859-16 have it (and unfortunately, I don't think all Unices will switch to Unicode in the meantime).... err... yes, I know that this is not every single person in Europe that uses PostgreSql, so it's not exactly 280m, but it's just a matter of time ! ;) I'll come back (on pgsql-hackers) later to ask a few questions regarding the full unicode support (normalisation, collation, regexes,...) on the server side :) Here is the patch ! Patrice. -- Patrice HÉDÉ ------------------------------- patrice à islande org ----- -- Isn't it weird how scientists can imagine all the matter of the universe exploding out of a dot smaller than the head of a pin, but they can't come up with a more evocative name for it than "The Big Bang" ? -- What would _you_ call the creation of the universe ? -- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes ------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
}
/* Add height of tallest header column */
extra_output_lines += extra_row_output_lines;
extra_row_output_lines = 0;
1999-11-05 00:14:30 +01:00
/* scan all cells, find maximum width, compute cell_count */
for (i = 0, ptr = cont->cells; *ptr; ptr++, i++, cell_count++)
{
int width,
nl_lines,
bytes_required;
pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
&width, &nl_lines, &bytes_required);
if (width > max_width[i % col_count])
max_width[i % col_count] = width;
if (nl_lines > max_nl_lines[i % col_count])
max_nl_lines[i % col_count] = nl_lines;
if (bytes_required > max_bytes[i % col_count])
max_bytes[i % col_count] = bytes_required;
2006-10-04 02:30:14 +02:00
width_average[i % col_count] += width;
Commit Patrice's patches except: > - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 > characters (characters with values >= 0x10000, which are encoded on > four bytes). Also, update mb/expected/unicode.out. This is necessary since the patches affetc the result of queries using UTF-8. --------------------------------------------------------------- Hi, I should have sent the patch earlier, but got delayed by other stuff. Anyway, here is the patch: - most of the functionality is only activated when MULTIBYTE is defined, - check valid UTF-8 characters, client-side only yet, and only on output, you still can send invalid UTF-8 to the server (so, it's only partly compliant to Unicode 3.1, but that's better than nothing). - formats with the correct number of columns (that's why I made it in the first place after all), but only for UNICODE. However, the code allows to plug-in routines for other encodings, as Tatsuo did for the other multibyte functions. - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 characters (characters with values >= 0x10000, which are encoded on four bytes). - doesn't depend on the locale capabilities of the glibc (useful for remote telnet). I would like somebody to check it closely, as it is my first patch to pgsql. Also, I created dummy .orig files, so that the two files I created are included, I hope that's the right way. Now, a lot of functionality is NOT included here, but I will keep that for 7.3 :) That includes all string checking on the server side (which will have to be a bit more optimised ;) ), and the input checking on the client side for UTF-8, though that should not be difficult. It's just to send the strings through mbvalidate() before sending them to the server. Strong checking on UTF-8 strings is mandatory to be compliant with Unicode 3.1+ . Do I have time to look for a patch to include iso-8859-15 for 7.2 ? The euro is coming 1. january 2002 (before 7.3 !) and over 280 millions people in Europe will need the euro sign and only iso-8859-15 and iso-8859-16 have it (and unfortunately, I don't think all Unices will switch to Unicode in the meantime).... err... yes, I know that this is not every single person in Europe that uses PostgreSql, so it's not exactly 280m, but it's just a matter of time ! ;) I'll come back (on pgsql-hackers) later to ask a few questions regarding the full unicode support (normalisation, collation, regexes,...) on the server side :) Here is the patch ! Patrice. -- Patrice HÉDÉ ------------------------------- patrice à islande org ----- -- Isn't it weird how scientists can imagine all the matter of the universe exploding out of a dot smaller than the head of a pin, but they can't come up with a more evocative name for it than "The Big Bang" ? -- What would _you_ call the creation of the universe ? -- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes ------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
}
/* If we have rows, compute average */
if (col_count != 0 && cell_count != 0)
{
int rows = cell_count / col_count;
for (i = 0; i < col_count; i++)
width_average[i] /= rows;
}
/* adjust the total display width based on border style */
1999-11-05 00:14:30 +01:00
if (opt_border == 0)
width_total = col_count;
1999-11-05 00:14:30 +01:00
else if (opt_border == 1)
width_total = col_count * 3 - ((col_count > 0) ? 1 : 0);
else
width_total = col_count * 3 + 1;
total_header_width = width_total;
1999-11-05 00:14:30 +01:00
for (i = 0; i < col_count; i++)
{
width_total += max_width[i];
total_header_width += width_header[i];
}
1999-11-05 00:14:30 +01:00
2006-10-04 02:30:14 +02:00
/*
* At this point: max_width[] contains the max width of each column,
* max_nl_lines[] contains the max number of lines in each column,
* max_bytes[] contains the maximum storage space for formatting strings,
* width_total contains the giant width sum. Now we allocate some memory
* for line pointers.
*/
for (i = 0; i < col_count; i++)
{
/* Add entry for ptr == NULL array termination */
col_lineptrs[i] = pg_malloc0((max_nl_lines[i] + 1) *
sizeof(**col_lineptrs));
2006-10-04 02:30:14 +02:00
format_buf[i] = pg_malloc(max_bytes[i] + 1);
2006-10-04 02:30:14 +02:00
col_lineptrs[i]->ptr = format_buf[i];
}
2006-10-04 02:30:14 +02:00
/* Default word wrap to the full width, i.e. no word wrap */
for (i = 0; i < col_count; i++)
width_wrap[i] = max_width[i];
/*
* Choose target output width: \pset columns, or $COLUMNS, or ioctl
*/
if (cont->opt->columns > 0)
output_columns = cont->opt->columns;
else if ((fout == stdout && isatty(fileno(stdout))) || is_pager)
{
if (cont->opt->env_columns > 0)
output_columns = cont->opt->env_columns;
#ifdef TIOCGWINSZ
else
{
struct winsize screen_size;
if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
output_columns = screen_size.ws_col;
}
#endif
}
if (cont->opt->format == PRINT_WRAPPED)
{
/*
* Optional optimized word wrap. Shrink columns with a high max/avg
* ratio. Slightly bias against wider columns. (Increases chance a
* narrow column will fit in its cell.) If available columns is
* positive... and greater than the width of the unshrinkable column
* headers
*/
if (output_columns > 0 && output_columns >= total_header_width)
{
/* While there is still excess width... */
while (width_total > output_columns)
{
double max_ratio = 0;
int worst_col = -1;
/*
* Find column that has the highest ratio of its maximum width
* compared to its average width. This tells us which column
* will produce the fewest wrapped values if shortened.
* width_wrap starts as equal to max_width.
*/
for (i = 0; i < col_count; i++)
{
if (width_average[i] && width_wrap[i] > width_header[i])
{
/* Penalize wide columns by 1% of their width */
double ratio;
2006-10-04 02:30:14 +02:00
ratio = (double) width_wrap[i] / width_average[i] +
max_width[i] * 0.01;
if (ratio > max_ratio)
{
max_ratio = ratio;
worst_col = i;
}
}
}
/* Exit loop if we can't squeeze any more. */
if (worst_col == -1)
break;
2006-10-04 02:30:14 +02:00
/* Decrease width of target column by one. */
width_wrap[worst_col]--;
width_total--;
}
}
}
/*
* If in expanded auto mode, we have now calculated the expected width, so
* we can now escape to vertical mode if necessary. If the output has
* only one column, the expanded format would be wider than the regular
* format, so don't use it in that case.
*/
if (cont->opt->expanded == 2 && output_columns > 0 && cont->ncolumns > 1 &&
(output_columns < total_header_width || output_columns < width_total))
{
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
print_aligned_vertical(cont, fout, is_pager);
goto cleanup;
}
/* If we wrapped beyond the display width, use the pager */
if (!is_pager && fout == stdout && output_columns > 0 &&
(output_columns < total_header_width || output_columns < width_total))
{
fout = PageOutput(INT_MAX, cont->opt); /* force pager */
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
is_pager = is_local_pager = true;
}
/* Check if newlines or our wrapping now need the pager */
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
if (!is_pager && fout == stdout)
{
/* scan all cells, find maximum width, compute cell_count */
for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++)
{
int width,
nl_lines,
bytes_required;
pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
&width, &nl_lines, &bytes_required);
/*
* A row can have both wrapping and newlines that cause it to
* display across multiple lines. We check for both cases below.
*/
if (width > 0 && width_wrap[i])
{
unsigned int extra_lines;
/* don't count the first line of nl_lines - it's not "extra" */
extra_lines = ((width - 1) / width_wrap[i]) + nl_lines - 1;
if (extra_lines > extra_row_output_lines)
extra_row_output_lines = extra_lines;
}
/* i is the current column number: increment with wrap */
if (++i >= col_count)
{
i = 0;
/* At last column of each row, add tallest column height */
extra_output_lines += extra_row_output_lines;
extra_row_output_lines = 0;
}
}
IsPagerNeeded(cont, extra_output_lines, false, &fout, &is_pager);
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
is_local_pager = is_pager;
}
/* time to output */
if (cont->opt->start_table)
1999-11-05 00:14:30 +01:00
{
/* print title */
if (cont->title && !opt_tuples_only)
{
int width,
height;
2006-10-04 02:30:14 +02:00
pg_wcssize((const unsigned char *) cont->title, strlen(cont->title),
encoding, &width, &height, NULL);
if (width >= width_total)
/* Aligned */
fprintf(fout, "%s\n", cont->title);
else
/* Centered */
fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "",
cont->title);
}
/* print headers */
if (!opt_tuples_only)
1999-11-05 00:14:30 +01:00
{
int more_col_wrapping;
int curr_nl_line;
2006-10-04 02:30:14 +02:00
if (opt_border == 2)
_print_horizontal_line(col_count, width_wrap, opt_border,
PRINT_RULE_TOP, format, fout);
for (i = 0; i < col_count; i++)
pg_wcsformat((const unsigned char *) cont->headers[i],
strlen(cont->headers[i]), encoding,
col_lineptrs[i], max_nl_lines[i]);
2006-10-04 02:30:14 +02:00
more_col_wrapping = col_count;
curr_nl_line = 0;
memset(header_done, false, col_count * sizeof(bool));
while (more_col_wrapping)
{
if (opt_border == 2)
fputs(dformat->leftvrule, fout);
Commit Patrice's patches except: > - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 > characters (characters with values >= 0x10000, which are encoded on > four bytes). Also, update mb/expected/unicode.out. This is necessary since the patches affetc the result of queries using UTF-8. --------------------------------------------------------------- Hi, I should have sent the patch earlier, but got delayed by other stuff. Anyway, here is the patch: - most of the functionality is only activated when MULTIBYTE is defined, - check valid UTF-8 characters, client-side only yet, and only on output, you still can send invalid UTF-8 to the server (so, it's only partly compliant to Unicode 3.1, but that's better than nothing). - formats with the correct number of columns (that's why I made it in the first place after all), but only for UNICODE. However, the code allows to plug-in routines for other encodings, as Tatsuo did for the other multibyte functions. - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 characters (characters with values >= 0x10000, which are encoded on four bytes). - doesn't depend on the locale capabilities of the glibc (useful for remote telnet). I would like somebody to check it closely, as it is my first patch to pgsql. Also, I created dummy .orig files, so that the two files I created are included, I hope that's the right way. Now, a lot of functionality is NOT included here, but I will keep that for 7.3 :) That includes all string checking on the server side (which will have to be a bit more optimised ;) ), and the input checking on the client side for UTF-8, though that should not be difficult. It's just to send the strings through mbvalidate() before sending them to the server. Strong checking on UTF-8 strings is mandatory to be compliant with Unicode 3.1+ . Do I have time to look for a patch to include iso-8859-15 for 7.2 ? The euro is coming 1. january 2002 (before 7.3 !) and over 280 millions people in Europe will need the euro sign and only iso-8859-15 and iso-8859-16 have it (and unfortunately, I don't think all Unices will switch to Unicode in the meantime).... err... yes, I know that this is not every single person in Europe that uses PostgreSql, so it's not exactly 280m, but it's just a matter of time ! ;) I'll come back (on pgsql-hackers) later to ask a few questions regarding the full unicode support (normalisation, collation, regexes,...) on the server side :) Here is the patch ! Patrice. -- Patrice HÉDÉ ------------------------------- patrice à islande org ----- -- Isn't it weird how scientists can imagine all the matter of the universe exploding out of a dot smaller than the head of a pin, but they can't come up with a more evocative name for it than "The Big Bang" ? -- What would _you_ call the creation of the universe ? -- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes ------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
for (i = 0; i < cont->ncolumns; i++)
{
struct lineptr *this_line = col_lineptrs[i] + curr_nl_line;
unsigned int nbspace;
2006-10-04 02:30:14 +02:00
if (opt_border != 0 ||
(!format->wrap_right_border && i > 0))
fputs(curr_nl_line ? format->header_nl_left : " ",
fout);
if (!header_done[i])
{
nbspace = width_wrap[i] - this_line->width;
/* centered */
fprintf(fout, "%-*s%s%-*s",
nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");
if (!(this_line + 1)->ptr)
{
more_col_wrapping--;
header_done[i] = 1;
}
}
else
fprintf(fout, "%*s", width_wrap[i], "");
if (opt_border != 0 || format->wrap_right_border)
fputs(!header_done[i] ? format->header_nl_right : " ",
fout);
if (opt_border != 0 && col_count > 0 && i < col_count - 1)
fputs(dformat->midvrule, fout);
}
curr_nl_line++;
if (opt_border == 2)
fputs(dformat->rightvrule, fout);
fputc('\n', fout);
1999-11-05 00:14:30 +01:00
}
_print_horizontal_line(col_count, width_wrap, opt_border,
PRINT_RULE_MIDDLE, format, fout);
1999-11-05 00:14:30 +01:00
}
}
/* print cells, one loop per row */
for (i = 0, ptr = cont->cells; *ptr; i += col_count, ptr += col_count)
1999-11-05 00:14:30 +01:00
{
bool more_lines;
if (cancel_pressed)
break;
/*
* Format each cell.
*/
for (j = 0; j < col_count; j++)
{
pg_wcsformat((const unsigned char *) ptr[j], strlen(ptr[j]), encoding,
col_lineptrs[j], max_nl_lines[j]);
curr_nl_line[j] = 0;
}
memset(bytes_output, 0, col_count * sizeof(int));
/*
* Each time through this loop, one display line is output. It can
* either be a full value or a partial value if embedded newlines
* exist or if 'format=wrapping' mode is enabled.
*/
do
1999-11-05 00:14:30 +01:00
{
more_lines = false;
/* left border */
1999-11-05 00:14:30 +01:00
if (opt_border == 2)
fputs(dformat->leftvrule, fout);
1999-11-05 00:14:30 +01:00
/* for each column */
for (j = 0; j < col_count; j++)
2005-10-15 04:49:52 +02:00
{
/* We have a valid array element, so index it */
struct lineptr *this_line = &col_lineptrs[j][curr_nl_line[j]];
int bytes_to_output;
int chars_to_output = width_wrap[j];
bool finalspaces = (opt_border == 2 ||
(col_count > 0 && j < col_count - 1));
/* Print left-hand wrap or newline mark */
if (opt_border != 0)
{
if (wrap[j] == PRINT_LINE_WRAP_WRAP)
fputs(format->wrap_left, fout);
else if (wrap[j] == PRINT_LINE_WRAP_NEWLINE)
fputs(format->nl_left, fout);
else
fputc(' ', fout);
}
if (!this_line->ptr)
{
/* Past newline lines so just pad for other columns */
if (finalspaces)
fprintf(fout, "%*s", chars_to_output, "");
}
else
{
/* Get strlen() of the characters up to width_wrap */
bytes_to_output =
strlen_max_width(this_line->ptr + bytes_output[j],
&chars_to_output, encoding);
/*
* If we exceeded width_wrap, it means the display width
* of a single character was wider than our target width.
* In that case, we have to pretend we are only printing
* the target display width and make the best of it.
*/
if (chars_to_output > width_wrap[j])
chars_to_output = width_wrap[j];
if (cont->aligns[j] == 'r') /* Right aligned cell */
{
/* spaces first */
fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
fputnbytes(fout,
(char *) (this_line->ptr + bytes_output[j]),
bytes_to_output);
}
2017-06-21 20:39:04 +02:00
else /* Left aligned cell */
{
/* spaces second */
fputnbytes(fout,
(char *) (this_line->ptr + bytes_output[j]),
bytes_to_output);
}
bytes_output[j] += bytes_to_output;
/* Do we have more text to wrap? */
if (*(this_line->ptr + bytes_output[j]) != '\0')
more_lines = true;
else
{
/* Advance to next newline line */
curr_nl_line[j]++;
if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
more_lines = true;
bytes_output[j] = 0;
}
}
2006-10-04 02:30:14 +02:00
/* Determine next line's wrap status for this column */
wrap[j] = PRINT_LINE_WRAP_NONE;
if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
{
if (bytes_output[j] != 0)
wrap[j] = PRINT_LINE_WRAP_WRAP;
else if (curr_nl_line[j] != 0)
wrap[j] = PRINT_LINE_WRAP_NEWLINE;
}
/*
* If left-aligned, pad out remaining space if needed (not
* last column, and/or wrap marks required).
*/
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
if (cont->aligns[j] != 'r') /* Left aligned cell */
{
if (finalspaces ||
wrap[j] == PRINT_LINE_WRAP_WRAP ||
2010-02-26 03:01:40 +01:00
wrap[j] == PRINT_LINE_WRAP_NEWLINE)
fprintf(fout, "%*s",
width_wrap[j] - chars_to_output, "");
}
/* Print right-hand wrap or newline mark */
if (wrap[j] == PRINT_LINE_WRAP_WRAP)
fputs(format->wrap_right, fout);
else if (wrap[j] == PRINT_LINE_WRAP_NEWLINE)
fputs(format->nl_right, fout);
else if (opt_border == 2 || (col_count > 0 && j < col_count - 1))
fputc(' ', fout);
/* Print column divider, if not the last column */
if (opt_border != 0 && (col_count > 0 && j < col_count - 1))
{
2010-02-26 03:01:40 +01:00
if (wrap[j + 1] == PRINT_LINE_WRAP_WRAP)
fputs(format->midvrule_wrap, fout);
2010-02-26 03:01:40 +01:00
else if (wrap[j + 1] == PRINT_LINE_WRAP_NEWLINE)
fputs(format->midvrule_nl, fout);
else if (col_lineptrs[j + 1][curr_nl_line[j + 1]].ptr == NULL)
fputs(format->midvrule_blank, fout);
else
fputs(dformat->midvrule, fout);
}
}
/* end-of-row border */
1999-11-05 00:14:30 +01:00
if (opt_border == 2)
fputs(dformat->rightvrule, fout);
1999-11-05 00:14:30 +01:00
fputc('\n', fout);
} while (more_lines);
}
if (cont->opt->stop_table)
{
printTableFooter *footers = footers_with_default(cont);
if (opt_border == 2 && !cancel_pressed)
_print_horizontal_line(col_count, width_wrap, opt_border,
PRINT_RULE_BOTTOM, format, fout);
/* print footers */
if (footers && !opt_tuples_only && !cancel_pressed)
{
printTableFooter *f;
for (f = footers; f; f = f->next)
fprintf(fout, "%s\n", f->data);
}
fputc('\n', fout);
}
cleanup:
1999-11-05 00:14:30 +01:00
/* clean up */
for (i = 0; i < col_count; i++)
{
free(col_lineptrs[i]);
free(format_buf[i]);
}
free(width_header);
free(width_average);
free(max_width);
free(width_wrap);
free(max_nl_lines);
free(curr_nl_line);
free(col_lineptrs);
free(max_bytes);
free(format_buf);
free(header_done);
free(bytes_output);
free(wrap);
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
if (is_local_pager)
ClosePager(fout);
}
static void
print_aligned_vertical_line(const printTextFormat *format,
const unsigned short opt_border,
unsigned long record,
unsigned int hwidth,
unsigned int dwidth,
printTextRule pos,
FILE *fout)
{
const printTextLineFormat *lformat = &format->lrule[pos];
2010-02-26 03:01:40 +01:00
unsigned int i;
int reclen = 0;
if (opt_border == 2)
fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule);
else if (opt_border == 1)
fputs(lformat->hrule, fout);
if (record)
{
if (opt_border == 0)
reclen = fprintf(fout, "* Record %lu", record);
else
reclen = fprintf(fout, "[ RECORD %lu ]", record);
}
if (opt_border != 2)
reclen++;
if (reclen < 0)
reclen = 0;
for (i = reclen; i < hwidth; i++)
fputs(opt_border > 0 ? lformat->hrule : " ", fout);
reclen -= hwidth;
if (opt_border > 0)
{
if (reclen-- <= 0)
fputs(lformat->hrule, fout);
if (reclen-- <= 0)
fputs(lformat->midvrule, fout);
if (reclen-- <= 0)
fputs(lformat->hrule, fout);
}
else
{
if (reclen-- <= 0)
fputc(' ', fout);
}
if (reclen < 0)
reclen = 0;
for (i = reclen; i < dwidth; i++)
fputs(opt_border > 0 ? lformat->hrule : " ", fout);
if (opt_border == 2)
fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule);
fputc('\n', fout);
}
static void
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
print_aligned_vertical(const printTableContent *cont,
FILE *fout, bool is_pager)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
const printTextFormat *format = get_line_style(cont->opt);
const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA];
int encoding = cont->opt->encoding;
unsigned long record = cont->opt->prior_records + 1;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
1999-11-05 00:14:30 +01:00
unsigned int i,
hwidth = 0,
dwidth = 0,
hheight = 1,
dheight = 1,
hformatsize = 0,
dformatsize = 0;
2006-10-04 02:30:14 +02:00
struct lineptr *hlineptr,
*dlineptr;
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
bool is_local_pager = false,
hmultiline = false,
dmultiline = false;
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
int output_columns = 0; /* Width of interactive console */
if (cancel_pressed)
return;
if (opt_border > 2)
opt_border = 2;
2006-10-04 02:30:14 +02:00
if (cont->cells[0] == NULL && cont->opt->start_table &&
cont->opt->stop_table)
{
printTableFooter *footers = footers_with_default(cont);
2015-03-28 00:50:55 +01:00
if (!opt_tuples_only && !cancel_pressed && footers)
{
printTableFooter *f;
for (f = footers; f; f = f->next)
fprintf(fout, "%s\n", f->data);
}
fputc('\n', fout);
return;
}
/*
* Deal with the pager here instead of in printTable(), because we could
* get here via print_aligned_text() in expanded auto mode, and so we have
* to recalculate the pager requirement based on vertical output.
*/
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
if (!is_pager)
{
IsPagerNeeded(cont, 0, true, &fout, &is_pager);
is_local_pager = is_pager;
}
/* Find the maximum dimensions for the headers */
for (i = 0; i < cont->ncolumns; i++)
Commit Patrice's patches except: > - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 > characters (characters with values >= 0x10000, which are encoded on > four bytes). Also, update mb/expected/unicode.out. This is necessary since the patches affetc the result of queries using UTF-8. --------------------------------------------------------------- Hi, I should have sent the patch earlier, but got delayed by other stuff. Anyway, here is the patch: - most of the functionality is only activated when MULTIBYTE is defined, - check valid UTF-8 characters, client-side only yet, and only on output, you still can send invalid UTF-8 to the server (so, it's only partly compliant to Unicode 3.1, but that's better than nothing). - formats with the correct number of columns (that's why I made it in the first place after all), but only for UNICODE. However, the code allows to plug-in routines for other encodings, as Tatsuo did for the other multibyte functions. - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 characters (characters with values >= 0x10000, which are encoded on four bytes). - doesn't depend on the locale capabilities of the glibc (useful for remote telnet). I would like somebody to check it closely, as it is my first patch to pgsql. Also, I created dummy .orig files, so that the two files I created are included, I hope that's the right way. Now, a lot of functionality is NOT included here, but I will keep that for 7.3 :) That includes all string checking on the server side (which will have to be a bit more optimised ;) ), and the input checking on the client side for UTF-8, though that should not be difficult. It's just to send the strings through mbvalidate() before sending them to the server. Strong checking on UTF-8 strings is mandatory to be compliant with Unicode 3.1+ . Do I have time to look for a patch to include iso-8859-15 for 7.2 ? The euro is coming 1. january 2002 (before 7.3 !) and over 280 millions people in Europe will need the euro sign and only iso-8859-15 and iso-8859-16 have it (and unfortunately, I don't think all Unices will switch to Unicode in the meantime).... err... yes, I know that this is not every single person in Europe that uses PostgreSql, so it's not exactly 280m, but it's just a matter of time ! ;) I'll come back (on pgsql-hackers) later to ask a few questions regarding the full unicode support (normalisation, collation, regexes,...) on the server side :) Here is the patch ! Patrice. -- Patrice HÉDÉ ------------------------------- patrice à islande org ----- -- Isn't it weird how scientists can imagine all the matter of the universe exploding out of a dot smaller than the head of a pin, but they can't come up with a more evocative name for it than "The Big Bang" ? -- What would _you_ call the creation of the universe ? -- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes ------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
{
int width,
height,
2006-10-04 02:30:14 +02:00
fs;
pg_wcssize((const unsigned char *) cont->headers[i], strlen(cont->headers[i]),
encoding, &width, &height, &fs);
if (width > hwidth)
hwidth = width;
if (height > hheight)
{
hheight = height;
hmultiline = true;
}
if (fs > hformatsize)
hformatsize = fs;
Commit Patrice's patches except: > - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 > characters (characters with values >= 0x10000, which are encoded on > four bytes). Also, update mb/expected/unicode.out. This is necessary since the patches affetc the result of queries using UTF-8. --------------------------------------------------------------- Hi, I should have sent the patch earlier, but got delayed by other stuff. Anyway, here is the patch: - most of the functionality is only activated when MULTIBYTE is defined, - check valid UTF-8 characters, client-side only yet, and only on output, you still can send invalid UTF-8 to the server (so, it's only partly compliant to Unicode 3.1, but that's better than nothing). - formats with the correct number of columns (that's why I made it in the first place after all), but only for UNICODE. However, the code allows to plug-in routines for other encodings, as Tatsuo did for the other multibyte functions. - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 characters (characters with values >= 0x10000, which are encoded on four bytes). - doesn't depend on the locale capabilities of the glibc (useful for remote telnet). I would like somebody to check it closely, as it is my first patch to pgsql. Also, I created dummy .orig files, so that the two files I created are included, I hope that's the right way. Now, a lot of functionality is NOT included here, but I will keep that for 7.3 :) That includes all string checking on the server side (which will have to be a bit more optimised ;) ), and the input checking on the client side for UTF-8, though that should not be difficult. It's just to send the strings through mbvalidate() before sending them to the server. Strong checking on UTF-8 strings is mandatory to be compliant with Unicode 3.1+ . Do I have time to look for a patch to include iso-8859-15 for 7.2 ? The euro is coming 1. january 2002 (before 7.3 !) and over 280 millions people in Europe will need the euro sign and only iso-8859-15 and iso-8859-16 have it (and unfortunately, I don't think all Unices will switch to Unicode in the meantime).... err... yes, I know that this is not every single person in Europe that uses PostgreSql, so it's not exactly 280m, but it's just a matter of time ! ;) I'll come back (on pgsql-hackers) later to ask a few questions regarding the full unicode support (normalisation, collation, regexes,...) on the server side :) Here is the patch ! Patrice. -- Patrice HÉDÉ ------------------------------- patrice à islande org ----- -- Isn't it weird how scientists can imagine all the matter of the universe exploding out of a dot smaller than the head of a pin, but they can't come up with a more evocative name for it than "The Big Bang" ? -- What would _you_ call the creation of the universe ? -- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes ------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
}
Commit Patrice's patches except: > - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 > characters (characters with values >= 0x10000, which are encoded on > four bytes). Also, update mb/expected/unicode.out. This is necessary since the patches affetc the result of queries using UTF-8. --------------------------------------------------------------- Hi, I should have sent the patch earlier, but got delayed by other stuff. Anyway, here is the patch: - most of the functionality is only activated when MULTIBYTE is defined, - check valid UTF-8 characters, client-side only yet, and only on output, you still can send invalid UTF-8 to the server (so, it's only partly compliant to Unicode 3.1, but that's better than nothing). - formats with the correct number of columns (that's why I made it in the first place after all), but only for UNICODE. However, the code allows to plug-in routines for other encodings, as Tatsuo did for the other multibyte functions. - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 characters (characters with values >= 0x10000, which are encoded on four bytes). - doesn't depend on the locale capabilities of the glibc (useful for remote telnet). I would like somebody to check it closely, as it is my first patch to pgsql. Also, I created dummy .orig files, so that the two files I created are included, I hope that's the right way. Now, a lot of functionality is NOT included here, but I will keep that for 7.3 :) That includes all string checking on the server side (which will have to be a bit more optimised ;) ), and the input checking on the client side for UTF-8, though that should not be difficult. It's just to send the strings through mbvalidate() before sending them to the server. Strong checking on UTF-8 strings is mandatory to be compliant with Unicode 3.1+ . Do I have time to look for a patch to include iso-8859-15 for 7.2 ? The euro is coming 1. january 2002 (before 7.3 !) and over 280 millions people in Europe will need the euro sign and only iso-8859-15 and iso-8859-16 have it (and unfortunately, I don't think all Unices will switch to Unicode in the meantime).... err... yes, I know that this is not every single person in Europe that uses PostgreSql, so it's not exactly 280m, but it's just a matter of time ! ;) I'll come back (on pgsql-hackers) later to ask a few questions regarding the full unicode support (normalisation, collation, regexes,...) on the server side :) Here is the patch ! Patrice. -- Patrice HÉDÉ ------------------------------- patrice à islande org ----- -- Isn't it weird how scientists can imagine all the matter of the universe exploding out of a dot smaller than the head of a pin, but they can't come up with a more evocative name for it than "The Big Bang" ? -- What would _you_ call the creation of the universe ? -- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes ------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
/* find longest data cell */
for (i = 0, ptr = cont->cells; *ptr; ptr++, i++)
{
int width,
height,
2006-10-04 02:30:14 +02:00
fs;
pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
&width, &height, &fs);
if (width > dwidth)
dwidth = width;
if (height > dheight)
{
dheight = height;
dmultiline = true;
}
if (fs > dformatsize)
dformatsize = fs;
Commit Patrice's patches except: > - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 > characters (characters with values >= 0x10000, which are encoded on > four bytes). Also, update mb/expected/unicode.out. This is necessary since the patches affetc the result of queries using UTF-8. --------------------------------------------------------------- Hi, I should have sent the patch earlier, but got delayed by other stuff. Anyway, here is the patch: - most of the functionality is only activated when MULTIBYTE is defined, - check valid UTF-8 characters, client-side only yet, and only on output, you still can send invalid UTF-8 to the server (so, it's only partly compliant to Unicode 3.1, but that's better than nothing). - formats with the correct number of columns (that's why I made it in the first place after all), but only for UNICODE. However, the code allows to plug-in routines for other encodings, as Tatsuo did for the other multibyte functions. - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1 characters (characters with values >= 0x10000, which are encoded on four bytes). - doesn't depend on the locale capabilities of the glibc (useful for remote telnet). I would like somebody to check it closely, as it is my first patch to pgsql. Also, I created dummy .orig files, so that the two files I created are included, I hope that's the right way. Now, a lot of functionality is NOT included here, but I will keep that for 7.3 :) That includes all string checking on the server side (which will have to be a bit more optimised ;) ), and the input checking on the client side for UTF-8, though that should not be difficult. It's just to send the strings through mbvalidate() before sending them to the server. Strong checking on UTF-8 strings is mandatory to be compliant with Unicode 3.1+ . Do I have time to look for a patch to include iso-8859-15 for 7.2 ? The euro is coming 1. january 2002 (before 7.3 !) and over 280 millions people in Europe will need the euro sign and only iso-8859-15 and iso-8859-16 have it (and unfortunately, I don't think all Unices will switch to Unicode in the meantime).... err... yes, I know that this is not every single person in Europe that uses PostgreSql, so it's not exactly 280m, but it's just a matter of time ! ;) I'll come back (on pgsql-hackers) later to ask a few questions regarding the full unicode support (normalisation, collation, regexes,...) on the server side :) Here is the patch ! Patrice. -- Patrice HÉDÉ ------------------------------- patrice à islande org ----- -- Isn't it weird how scientists can imagine all the matter of the universe exploding out of a dot smaller than the head of a pin, but they can't come up with a more evocative name for it than "The Big Bang" ? -- What would _you_ call the creation of the universe ? -- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes ------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
}
2006-10-04 02:30:14 +02:00
/*
* We now have all the information we need to setup the formatting
* structures
*/
dlineptr = pg_malloc((sizeof(*dlineptr)) * (dheight + 1));
hlineptr = pg_malloc((sizeof(*hlineptr)) * (hheight + 1));
2006-10-04 02:30:14 +02:00
dlineptr->ptr = pg_malloc(dformatsize);
hlineptr->ptr = pg_malloc(hformatsize);
1999-11-05 00:14:30 +01:00
if (cont->opt->start_table)
{
/* print title */
if (!opt_tuples_only && cont->title)
fprintf(fout, "%s\n", cont->title);
}
/*
* Choose target output width: \pset columns, or $COLUMNS, or ioctl
*/
if (cont->opt->columns > 0)
output_columns = cont->opt->columns;
else if ((fout == stdout && isatty(fileno(stdout))) || is_pager)
{
if (cont->opt->env_columns > 0)
output_columns = cont->opt->env_columns;
#ifdef TIOCGWINSZ
else
{
struct winsize screen_size;
if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
output_columns = screen_size.ws_col;
}
#endif
}
/*
* Calculate available width for data in wrapped mode
*/
if (cont->opt->format == PRINT_WRAPPED)
{
unsigned int swidth,
rwidth = 0,
newdwidth;
if (opt_border == 0)
{
/*
* For border = 0, one space in the middle. (If we discover we
* need to wrap, the spacer column will be replaced by a wrap
* marker, and we'll make room below for another wrap marker at
* the end of the line. But for now, assume no wrap is needed.)
*/
swidth = 1;
/* We might need a column for header newline markers, too */
if (hmultiline)
swidth++;
}
else if (opt_border == 1)
{
/*
* For border = 1, two spaces and a vrule in the middle. (As
* above, we might need one more column for a wrap marker.)
*/
swidth = 3;
/* We might need a column for left header newline markers, too */
if (hmultiline && (format == &pg_asciiformat_old))
swidth++;
}
else
{
/*
* For border = 2, two more for the vrules at the beginning and
* end of the lines, plus spacer columns adjacent to these. (We
* won't need extra columns for wrap/newline markers, we'll just
* repurpose the spacers.)
*/
swidth = 7;
}
/* Reserve a column for data newline indicators, too, if needed */
if (dmultiline &&
opt_border < 2 && format != &pg_asciiformat_old)
swidth++;
/* Determine width required for record header lines */
if (!opt_tuples_only)
{
if (cont->nrows > 0)
rwidth = 1 + (int) log10(cont->nrows);
if (opt_border == 0)
rwidth += 9; /* "* RECORD " */
else if (opt_border == 1)
rwidth += 12; /* "-[ RECORD ]" */
else
rwidth += 15; /* "+-[ RECORD ]-+" */
}
/* We might need to do the rest of the calculation twice */
for (;;)
{
unsigned int width;
/* Total width required to not wrap data */
width = hwidth + swidth + dwidth;
/* ... and not the header lines, either */
if (width < rwidth)
width = rwidth;
if (output_columns > 0)
{
unsigned int min_width;
/* Minimum acceptable width: room for just 3 columns of data */
min_width = hwidth + swidth + 3;
/* ... but not less than what the record header lines need */
if (min_width < rwidth)
min_width = rwidth;
if (output_columns >= width)
{
/* Plenty of room, use native data width */
/* (but at least enough for the record header lines) */
newdwidth = width - hwidth - swidth;
}
else if (output_columns < min_width)
{
/* Set data width to match min_width */
newdwidth = min_width - hwidth - swidth;
}
else
{
/* Set data width to match output_columns */
newdwidth = output_columns - hwidth - swidth;
}
}
else
{
/* Don't know the wrap limit, so use native data width */
/* (but at least enough for the record header lines) */
newdwidth = width - hwidth - swidth;
}
2015-05-24 03:35:49 +02:00
/*
* If we will need to wrap data and didn't already allocate a data
* newline/wrap marker column, do so and recompute.
*/
if (newdwidth < dwidth && !dmultiline &&
opt_border < 2 && format != &pg_asciiformat_old)
{
dmultiline = true;
swidth++;
}
else
break;
}
dwidth = newdwidth;
}
1999-11-05 00:14:30 +01:00
/* print records */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
1999-11-05 00:14:30 +01:00
{
printTextRule pos;
int dline,
hline,
2006-10-04 02:30:14 +02:00
dcomplete,
hcomplete,
offset,
chars_to_output;
2006-10-04 02:30:14 +02:00
if (cancel_pressed)
break;
if (i == 0)
2010-02-26 03:01:40 +01:00
pos = PRINT_RULE_TOP;
else
pos = PRINT_RULE_MIDDLE;
/* Print record header (e.g. "[ RECORD N ]") above each record */
if (i % cont->ncolumns == 0)
1999-11-05 00:14:30 +01:00
{
unsigned int lhwidth = hwidth;
2015-05-24 03:35:49 +02:00
if ((opt_border < 2) &&
2014-08-22 03:37:41 +02:00
(hmultiline) &&
(format == &pg_asciiformat_old))
2015-05-24 03:35:49 +02:00
lhwidth++; /* for newline indicators */
if (!opt_tuples_only)
print_aligned_vertical_line(format, opt_border, record++,
lhwidth, dwidth, pos, fout);
else if (i != 0 || !cont->opt->start_table || opt_border == 2)
print_aligned_vertical_line(format, opt_border, 0, lhwidth,
dwidth, pos, fout);
1999-11-05 00:14:30 +01:00
}
/* Format the header */
pg_wcsformat((const unsigned char *) cont->headers[i % cont->ncolumns],
strlen(cont->headers[i % cont->ncolumns]),
encoding, hlineptr, hheight);
/* Format the data */
pg_wcsformat((const unsigned char *) *ptr, strlen(*ptr), encoding,
dlineptr, dheight);
2006-10-04 02:30:14 +02:00
/*
* Loop through header and data in parallel dealing with newlines and
* wrapped lines until they're both exhausted
*/
dline = hline = 0;
dcomplete = hcomplete = 0;
offset = 0;
chars_to_output = dlineptr[dline].width;
while (!dcomplete || !hcomplete)
{
/* Left border */
if (opt_border == 2)
fprintf(fout, "%s", dformat->leftvrule);
/* Header (never wrapped so just need to deal with newlines) */
if (!hcomplete)
{
int swidth = hwidth,
target_width = hwidth;
2015-05-24 03:35:49 +02:00
/*
* Left spacer or new line indicator
*/
if ((opt_border == 2) ||
(hmultiline && (format == &pg_asciiformat_old)))
fputs(hline ? format->header_nl_left : " ", fout);
2015-05-24 03:35:49 +02:00
/*
* Header text
*/
strlen_max_width(hlineptr[hline].ptr, &target_width,
encoding);
fprintf(fout, "%-s", hlineptr[hline].ptr);
2006-10-04 02:30:14 +02:00
/*
* Spacer
*/
swidth -= target_width;
if (swidth > 0)
fprintf(fout, "%*s", swidth, " ");
2014-08-22 03:37:41 +02:00
/*
* New line indicator or separator's space
*/
if (hlineptr[hline + 1].ptr)
{
/* More lines after this one due to a newline */
if ((opt_border > 0) ||
(hmultiline && (format != &pg_asciiformat_old)))
fputs(format->header_nl_right, fout);
hline++;
2014-04-30 03:35:07 +02:00
}
else
{
/* This was the last line of the header */
if ((opt_border > 0) ||
(hmultiline && (format != &pg_asciiformat_old)))
fputs(" ", fout);
hcomplete = 1;
}
}
else
{
unsigned int swidth = hwidth + opt_border;
2015-05-24 03:35:49 +02:00
if ((opt_border < 2) &&
(hmultiline) &&
(format == &pg_asciiformat_old))
swidth++;
2014-08-22 03:37:41 +02:00
if ((opt_border == 0) &&
(format != &pg_asciiformat_old) &&
(hmultiline))
swidth++;
fprintf(fout, "%*s", swidth, " ");
}
2006-10-04 02:30:14 +02:00
/* Separator */
if (opt_border > 0)
{
if (offset)
fputs(format->midvrule_wrap, fout);
else if (dline == 0)
fputs(dformat->midvrule, fout);
else
fputs(format->midvrule_nl, fout);
}
/* Data */
if (!dcomplete)
{
int target_width = dwidth,
bytes_to_output,
swidth = dwidth;
/*
* Left spacer or wrap indicator
*/
fputs(offset == 0 ? " " : format->wrap_left, fout);
2006-10-04 02:30:14 +02:00
/*
* Data text
*/
bytes_to_output = strlen_max_width(dlineptr[dline].ptr + offset,
&target_width, encoding);
fputnbytes(fout, (char *) (dlineptr[dline].ptr + offset),
bytes_to_output);
chars_to_output -= target_width;
offset += bytes_to_output;
/* Spacer */
swidth -= target_width;
if (chars_to_output)
{
/* continuing a wrapped column */
if ((opt_border > 1) ||
(dmultiline && (format != &pg_asciiformat_old)))
{
if (swidth > 0)
fprintf(fout, "%*s", swidth, " ");
fputs(format->wrap_right, fout);
}
}
else if (dlineptr[dline + 1].ptr)
{
/* reached a newline in the column */
if ((opt_border > 1) ||
(dmultiline && (format != &pg_asciiformat_old)))
{
if (swidth > 0)
fprintf(fout, "%*s", swidth, " ");
fputs(format->nl_right, fout);
}
dline++;
offset = 0;
chars_to_output = dlineptr[dline].width;
}
else
{
/* reached the end of the cell */
if (opt_border > 1)
{
if (swidth > 0)
fprintf(fout, "%*s", swidth, " ");
fputs(" ", fout);
}
2006-10-04 02:30:14 +02:00
dcomplete = 1;
}
/* Right border */
if (opt_border == 2)
fputs(dformat->rightvrule, fout);
fputs("\n", fout);
2006-10-04 02:30:14 +02:00
}
else
{
/*
* data exhausted (this can occur if header is longer than the
* data due to newlines in the header)
*/
2006-10-04 02:30:14 +02:00
if (opt_border < 2)
fputs("\n", fout);
2006-10-04 02:30:14 +02:00
else
fprintf(fout, "%*s %s\n", dwidth, "", dformat->rightvrule);
2006-10-04 02:30:14 +02:00
}
}
1999-11-05 00:14:30 +01:00
}
if (cont->opt->stop_table)
{
if (opt_border == 2 && !cancel_pressed)
print_aligned_vertical_line(format, opt_border, 0, hwidth, dwidth,
PRINT_RULE_BOTTOM, fout);
/* print footers */
if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
{
printTableFooter *f;
if (opt_border < 2)
fputc('\n', fout);
for (f = cont->footers; f; f = f->next)
fprintf(fout, "%s\n", f->data);
}
fputc('\n', fout);
1999-11-05 00:14:30 +01:00
}
free(hlineptr->ptr);
free(dlineptr->ptr);
free(hlineptr);
free(dlineptr);
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
if (is_local_pager)
ClosePager(fout);
}
/**********************/
/* CSV format */
/**********************/
static void
csv_escaped_print(const char *str, FILE *fout)
{
const char *p;
fputc('"', fout);
for (p = str; *p; p++)
{
if (*p == '"')
fputc('"', fout); /* double quotes are doubled */
fputc(*p, fout);
}
fputc('"', fout);
}
static void
csv_print_field(const char *str, FILE *fout, char sep)
{
/*----------------
* Enclose and escape field contents when one of these conditions is met:
* - the field separator is found in the contents.
* - the field contains a CR or LF.
* - the field contains a double quote.
* - the field is exactly "\.".
* - the field separator is either "\" or ".".
* The last two cases prevent producing a line that the server's COPY
* command would interpret as an end-of-data marker. We only really
* need to ensure that the complete line isn't exactly "\.", but for
* simplicity we apply stronger restrictions here.
*----------------
*/
if (strchr(str, sep) != NULL ||
strcspn(str, "\r\n\"") != strlen(str) ||
strcmp(str, "\\.") == 0 ||
sep == '\\' || sep == '.')
csv_escaped_print(str, fout);
else
fputs(str, fout);
}
static void
print_csv_text(const printTableContent *cont, FILE *fout)
{
const char *const *ptr;
int i;
if (cancel_pressed)
return;
/*
* The title and footer are never printed in csv format. The header is
* printed if opt_tuples_only is false.
*
* Despite RFC 4180 saying that end of lines are CRLF, terminate lines
* with '\n', which prints out as the system-dependent EOL string in text
* mode (typically LF on Unix and CRLF on Windows).
*/
if (cont->opt->start_table && !cont->opt->tuples_only)
{
/* print headers */
for (ptr = cont->headers; *ptr; ptr++)
{
if (ptr != cont->headers)
fputc(cont->opt->csvFieldSep[0], fout);
csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
}
fputc('\n', fout);
}
/* print cells */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
{
csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
if ((i + 1) % cont->ncolumns)
fputc(cont->opt->csvFieldSep[0], fout);
else
fputc('\n', fout);
}
}
static void
print_csv_vertical(const printTableContent *cont, FILE *fout)
{
const char *const *ptr;
int i;
/* print records */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
{
if (cancel_pressed)
return;
/* print name of column */
csv_print_field(cont->headers[i % cont->ncolumns], fout,
cont->opt->csvFieldSep[0]);
/* print field separator */
fputc(cont->opt->csvFieldSep[0], fout);
/* print field value */
csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
fputc('\n', fout);
}
}
/**********************/
/* HTML */
/**********************/
void
1999-11-05 00:14:30 +01:00
html_escaped_print(const char *in, FILE *fout)
{
1999-11-05 00:14:30 +01:00
const char *p;
2005-10-15 04:49:52 +02:00
bool leading_space = true;
1999-11-05 00:14:30 +01:00
for (p = in; *p; p++)
{
1999-11-05 00:14:30 +01:00
switch (*p)
{
case '&':
fputs("&amp;", fout);
break;
case '<':
fputs("&lt;", fout);
break;
case '>':
fputs("&gt;", fout);
break;
case '\n':
fputs("<br />\n", fout);
break;
case '"':
fputs("&quot;", fout);
break;
case ' ':
/* protect leading space, for EXPLAIN output */
if (leading_space)
fputs("&nbsp;", fout);
else
fputs(" ", fout);
break;
1999-11-05 00:14:30 +01:00
default:
fputc(*p, fout);
}
if (*p != ' ')
leading_space = false;
}
}
static void
print_html_text(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
const char *opt_table_attr = cont->opt->tableAttr;
1999-11-05 00:14:30 +01:00
unsigned int i;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
1999-11-05 00:14:30 +01:00
if (cancel_pressed)
return;
if (cont->opt->start_table)
1999-11-05 00:14:30 +01:00
{
fprintf(fout, "<table border=\"%d\"", opt_border);
if (opt_table_attr)
fprintf(fout, " %s", opt_table_attr);
fputs(">\n", fout);
/* print title */
if (!opt_tuples_only && cont->title)
{
fputs(" <caption>", fout);
html_escaped_print(cont->title, fout);
fputs("</caption>\n", fout);
}
/* print headers */
if (!opt_tuples_only)
1999-11-05 00:14:30 +01:00
{
fputs(" <tr>\n", fout);
for (ptr = cont->headers; *ptr; ptr++)
{
fputs(" <th align=\"center\">", fout);
html_escaped_print(*ptr, fout);
fputs("</th>\n", fout);
}
fputs(" </tr>\n", fout);
1999-11-05 00:14:30 +01:00
}
}
/* print cells */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
1999-11-05 00:14:30 +01:00
{
if (i % cont->ncolumns == 0)
{
if (cancel_pressed)
break;
fputs(" <tr valign=\"top\">\n", fout);
}
1999-11-05 00:14:30 +01:00
fprintf(fout, " <td align=\"%s\">", cont->aligns[(i) % cont->ncolumns] == 'r' ? "right" : "left");
/* is string only whitespace? */
2005-10-15 04:49:52 +02:00
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs("&nbsp; ", fout);
1999-11-05 00:14:30 +01:00
else
html_escaped_print(*ptr, fout);
1999-11-05 00:14:30 +01:00
fputs("</td>\n", fout);
if ((i + 1) % cont->ncolumns == 0)
1999-11-05 00:14:30 +01:00
fputs(" </tr>\n", fout);
}
if (cont->opt->stop_table)
{
printTableFooter *footers = footers_with_default(cont);
fputs("</table>\n", fout);
/* print footers */
if (!opt_tuples_only && footers != NULL && !cancel_pressed)
1999-11-05 00:14:30 +01:00
{
printTableFooter *f;
fputs("<p>", fout);
for (f = footers; f; f = f->next)
{
html_escaped_print(f->data, fout);
fputs("<br />\n", fout);
}
fputs("</p>", fout);
1999-11-05 00:14:30 +01:00
}
fputc('\n', fout);
}
}
static void
print_html_vertical(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
const char *opt_table_attr = cont->opt->tableAttr;
unsigned long record = cont->opt->prior_records + 1;
1999-11-05 00:14:30 +01:00
unsigned int i;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
1999-11-05 00:14:30 +01:00
if (cancel_pressed)
return;
if (cont->opt->start_table)
{
fprintf(fout, "<table border=\"%d\"", opt_border);
if (opt_table_attr)
fprintf(fout, " %s", opt_table_attr);
fputs(">\n", fout);
/* print title */
if (!opt_tuples_only && cont->title)
{
fputs(" <caption>", fout);
html_escaped_print(cont->title, fout);
fputs("</caption>\n", fout);
}
}
1999-11-05 00:14:30 +01:00
/* print records */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
1999-11-05 00:14:30 +01:00
{
if (i % cont->ncolumns == 0)
1999-11-05 00:14:30 +01:00
{
if (cancel_pressed)
break;
if (!opt_tuples_only)
fprintf(fout,
"\n <tr><td colspan=\"2\" align=\"center\">Record %lu</td></tr>\n",
record++);
1999-11-05 00:14:30 +01:00
else
fputs("\n <tr><td colspan=\"2\">&nbsp;</td></tr>\n", fout);
1999-11-05 00:14:30 +01:00
}
fputs(" <tr valign=\"top\">\n"
1999-11-05 00:14:30 +01:00
" <th>", fout);
html_escaped_print(cont->headers[i % cont->ncolumns], fout);
1999-11-05 00:14:30 +01:00
fputs("</th>\n", fout);
fprintf(fout, " <td align=\"%s\">", cont->aligns[i % cont->ncolumns] == 'r' ? "right" : "left");
/* is string only whitespace? */
2005-10-15 04:49:52 +02:00
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs("&nbsp; ", fout);
1999-11-05 00:14:30 +01:00
else
html_escaped_print(*ptr, fout);
1999-11-05 00:14:30 +01:00
fputs("</td>\n </tr>\n", fout);
}
if (cont->opt->stop_table)
{
fputs("</table>\n", fout);
/* print footers */
if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
1999-11-05 00:14:30 +01:00
{
printTableFooter *f;
fputs("<p>", fout);
for (f = cont->footers; f; f = f->next)
{
html_escaped_print(f->data, fout);
fputs("<br />\n", fout);
}
fputs("</p>", fout);
1999-11-05 00:14:30 +01:00
}
fputc('\n', fout);
}
}
/*************************/
/* ASCIIDOC */
/*************************/
static void
asciidoc_escaped_print(const char *in, FILE *fout)
{
const char *p;
2015-05-24 03:35:49 +02:00
for (p = in; *p; p++)
{
2015-05-24 03:35:49 +02:00
switch (*p)
{
case '|':
fputs("\\|", fout);
break;
default:
fputc(*p, fout);
}
}
}
static void
print_asciidoc_text(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
unsigned int i;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
if (cancel_pressed)
return;
if (cont->opt->start_table)
{
/* print table in new paragraph - enforce preliminary new line */
fputs("\n", fout);
/* print title */
if (!opt_tuples_only && cont->title)
{
fputs(".", fout);
fputs(cont->title, fout);
fputs("\n", fout);
}
/* print table [] header definition */
fprintf(fout, "[%scols=\"", !opt_tuples_only ? "options=\"header\"," : "");
2015-05-24 03:35:49 +02:00
for (i = 0; i < cont->ncolumns; i++)
{
if (i != 0)
fputs(",", fout);
fprintf(fout, "%s", cont->aligns[(i) % cont->ncolumns] == 'r' ? ">l" : "<l");
}
fputs("\"", fout);
switch (opt_border)
{
case 0:
fputs(",frame=\"none\",grid=\"none\"", fout);
break;
case 1:
fputs(",frame=\"none\"", fout);
break;
case 2:
fputs(",frame=\"all\",grid=\"all\"", fout);
break;
}
fputs("]\n", fout);
fputs("|====\n", fout);
/* print headers */
if (!opt_tuples_only)
{
for (ptr = cont->headers; *ptr; ptr++)
{
if (ptr != cont->headers)
fputs(" ", fout);
fputs("^l|", fout);
asciidoc_escaped_print(*ptr, fout);
}
fputs("\n", fout);
}
}
/* print cells */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
{
if (i % cont->ncolumns == 0)
{
if (cancel_pressed)
break;
}
if (i % cont->ncolumns != 0)
fputs(" ", fout);
fputs("|", fout);
/* protect against needless spaces */
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
{
if ((i + 1) % cont->ncolumns != 0)
fputs(" ", fout);
}
else
asciidoc_escaped_print(*ptr, fout);
if ((i + 1) % cont->ncolumns == 0)
fputs("\n", fout);
}
fputs("|====\n", fout);
if (cont->opt->stop_table)
{
printTableFooter *footers = footers_with_default(cont);
/* print footers */
if (!opt_tuples_only && footers != NULL && !cancel_pressed)
{
printTableFooter *f;
fputs("\n....\n", fout);
for (f = footers; f; f = f->next)
{
fputs(f->data, fout);
fputs("\n", fout);
}
fputs("....\n", fout);
}
}
}
static void
print_asciidoc_vertical(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
unsigned long record = cont->opt->prior_records + 1;
unsigned int i;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
if (cancel_pressed)
return;
if (cont->opt->start_table)
{
/* print table in new paragraph - enforce preliminary new line */
fputs("\n", fout);
/* print title */
if (!opt_tuples_only && cont->title)
{
fputs(".", fout);
fputs(cont->title, fout);
fputs("\n", fout);
}
/* print table [] header definition */
fputs("[cols=\"h,l\"", fout);
switch (opt_border)
{
case 0:
fputs(",frame=\"none\",grid=\"none\"", fout);
break;
case 1:
fputs(",frame=\"none\"", fout);
break;
case 2:
fputs(",frame=\"all\",grid=\"all\"", fout);
2015-05-24 03:35:49 +02:00
break;
}
fputs("]\n", fout);
fputs("|====\n", fout);
}
/* print records */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
{
if (i % cont->ncolumns == 0)
{
if (cancel_pressed)
break;
if (!opt_tuples_only)
fprintf(fout,
"2+^|Record %lu\n",
record++);
else
fputs("2+|\n", fout);
}
fputs("<l|", fout);
asciidoc_escaped_print(cont->headers[i % cont->ncolumns], fout);
fprintf(fout, " %s|", cont->aligns[i % cont->ncolumns] == 'r' ? ">l" : "<l");
/* is string only whitespace? */
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs(" ", fout);
else
asciidoc_escaped_print(*ptr, fout);
fputs("\n", fout);
}
fputs("|====\n", fout);
if (cont->opt->stop_table)
{
/* print footers */
if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
{
printTableFooter *f;
fputs("\n....\n", fout);
for (f = cont->footers; f; f = f->next)
{
fputs(f->data, fout);
fputs("\n", fout);
}
fputs("....\n", fout);
}
}
}
/*************************/
/* LaTeX */
/*************************/
static void
1999-11-05 00:14:30 +01:00
latex_escaped_print(const char *in, FILE *fout)
{
1999-11-05 00:14:30 +01:00
const char *p;
for (p = in; *p; p++)
switch (*p)
{
/*
* We convert ASCII characters per the recommendations in
* Scott Pakin's "The Comprehensive LATEX Symbol List",
* available from CTAN. For non-ASCII, you're on your own.
*/
case '#':
fputs("\\#", fout);
break;
case '$':
fputs("\\$", fout);
1999-11-05 00:14:30 +01:00
break;
case '%':
fputs("\\%", fout);
break;
case '&':
fputs("\\&", fout);
break;
case '<':
fputs("\\textless{}", fout);
break;
case '>':
fputs("\\textgreater{}", fout);
break;
case '\\':
fputs("\\textbackslash{}", fout);
break;
case '^':
fputs("\\^{}", fout);
1999-11-05 00:14:30 +01:00
break;
case '_':
fputs("\\_", fout);
break;
1999-11-05 00:14:30 +01:00
case '{':
fputs("\\{", fout);
break;
case '|':
fputs("\\textbar{}", fout);
break;
1999-11-05 00:14:30 +01:00
case '}':
fputs("\\}", fout);
break;
case '~':
fputs("\\~{}", fout);
1999-11-05 00:14:30 +01:00
break;
case '\n':
/* This is not right, but doing it right seems too hard */
1999-11-05 00:14:30 +01:00
fputs("\\\\", fout);
break;
default:
fputc(*p, fout);
}
}
static void
print_latex_text(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
1999-11-05 00:14:30 +01:00
unsigned int i;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
1999-11-05 00:14:30 +01:00
if (cancel_pressed)
return;
1999-11-05 00:14:30 +01:00
if (opt_border > 3)
opt_border = 3;
if (cont->opt->start_table)
1999-11-05 00:14:30 +01:00
{
/* print title */
if (!opt_tuples_only && cont->title)
{
fputs("\\begin{center}\n", fout);
latex_escaped_print(cont->title, fout);
fputs("\n\\end{center}\n\n", fout);
}
/* begin environment and set alignments and borders */
fputs("\\begin{tabular}{", fout);
if (opt_border >= 2)
fputs("| ", fout);
for (i = 0; i < cont->ncolumns; i++)
{
fputc(*(cont->aligns + i), fout);
if (opt_border != 0 && i < cont->ncolumns - 1)
fputs(" | ", fout);
}
if (opt_border >= 2)
fputs(" |", fout);
fputs("}\n", fout);
if (!opt_tuples_only && opt_border >= 2)
fputs("\\hline\n", fout);
/* print headers */
if (!opt_tuples_only)
1999-11-05 00:14:30 +01:00
{
for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
{
if (i != 0)
fputs(" & ", fout);
fputs("\\textit{", fout);
latex_escaped_print(*ptr, fout);
fputc('}', fout);
}
fputs(" \\\\\n", fout);
fputs("\\hline\n", fout);
1999-11-05 00:14:30 +01:00
}
}
/* print cells */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
1999-11-05 00:14:30 +01:00
{
latex_escaped_print(*ptr, fout);
if ((i + 1) % cont->ncolumns == 0)
{
1999-11-05 00:14:30 +01:00
fputs(" \\\\\n", fout);
if (opt_border == 3)
fputs("\\hline\n", fout);
if (cancel_pressed)
break;
}
1999-11-05 00:14:30 +01:00
else
fputs(" & ", fout);
}
if (cont->opt->stop_table)
{
printTableFooter *footers = footers_with_default(cont);
if (opt_border == 2)
fputs("\\hline\n", fout);
fputs("\\end{tabular}\n\n\\noindent ", fout);
/* print footers */
if (footers && !opt_tuples_only && !cancel_pressed)
1999-11-05 00:14:30 +01:00
{
printTableFooter *f;
for (f = footers; f; f = f->next)
{
latex_escaped_print(f->data, fout);
fputs(" \\\\\n", fout);
}
1999-11-05 00:14:30 +01:00
}
fputc('\n', fout);
}
}
/*************************/
/* LaTeX longtable */
/*************************/
static void
print_latex_longtable_text(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
unsigned int i;
const char *opt_table_attr = cont->opt->tableAttr;
const char *next_opt_table_attr_char = opt_table_attr;
const char *last_opt_table_attr_char = NULL;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
if (cancel_pressed)
return;
if (opt_border > 3)
opt_border = 3;
if (cont->opt->start_table)
{
/* begin environment and set alignments and borders */
fputs("\\begin{longtable}{", fout);
if (opt_border >= 2)
fputs("| ", fout);
for (i = 0; i < cont->ncolumns; i++)
{
/* longtable supports either a width (p) or an alignment (l/r) */
/* Are we left-justified and was a proportional width specified? */
if (*(cont->aligns + i) == 'l' && opt_table_attr)
{
#define LONGTABLE_WHITESPACE " \t\n"
/* advance over whitespace */
next_opt_table_attr_char += strspn(next_opt_table_attr_char,
LONGTABLE_WHITESPACE);
/* We have a value? */
if (next_opt_table_attr_char[0] != '\0')
{
fputs("p{", fout);
fwrite(next_opt_table_attr_char, strcspn(next_opt_table_attr_char,
LONGTABLE_WHITESPACE), 1, fout);
last_opt_table_attr_char = next_opt_table_attr_char;
next_opt_table_attr_char += strcspn(next_opt_table_attr_char,
LONGTABLE_WHITESPACE);
fputs("\\textwidth}", fout);
}
/* use previous value */
else if (last_opt_table_attr_char != NULL)
{
fputs("p{", fout);
fwrite(last_opt_table_attr_char, strcspn(last_opt_table_attr_char,
LONGTABLE_WHITESPACE), 1, fout);
fputs("\\textwidth}", fout);
}
else
fputc('l', fout);
}
else
fputc(*(cont->aligns + i), fout);
if (opt_border != 0 && i < cont->ncolumns - 1)
fputs(" | ", fout);
}
if (opt_border >= 2)
fputs(" |", fout);
fputs("}\n", fout);
/* print headers */
if (!opt_tuples_only)
{
/* firsthead */
if (opt_border >= 2)
fputs("\\toprule\n", fout);
for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
{
if (i != 0)
fputs(" & ", fout);
fputs("\\small\\textbf{\\textit{", fout);
latex_escaped_print(*ptr, fout);
fputs("}}", fout);
}
fputs(" \\\\\n", fout);
fputs("\\midrule\n\\endfirsthead\n", fout);
/* secondary heads */
if (opt_border >= 2)
fputs("\\toprule\n", fout);
for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
{
if (i != 0)
fputs(" & ", fout);
fputs("\\small\\textbf{\\textit{", fout);
latex_escaped_print(*ptr, fout);
fputs("}}", fout);
}
fputs(" \\\\\n", fout);
/* If the line under the row already appeared, don't do another */
if (opt_border != 3)
fputs("\\midrule\n", fout);
fputs("\\endhead\n", fout);
/* table name, caption? */
if (!opt_tuples_only && cont->title)
{
/* Don't output if we are printing a line under each row */
if (opt_border == 2)
fputs("\\bottomrule\n", fout);
fputs("\\caption[", fout);
latex_escaped_print(cont->title, fout);
fputs(" (Continued)]{", fout);
latex_escaped_print(cont->title, fout);
fputs("}\n\\endfoot\n", fout);
if (opt_border == 2)
fputs("\\bottomrule\n", fout);
fputs("\\caption[", fout);
latex_escaped_print(cont->title, fout);
fputs("]{", fout);
latex_escaped_print(cont->title, fout);
fputs("}\n\\endlastfoot\n", fout);
}
/* output bottom table line? */
else if (opt_border >= 2)
{
fputs("\\bottomrule\n\\endfoot\n", fout);
fputs("\\bottomrule\n\\endlastfoot\n", fout);
}
}
}
/* print cells */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
{
/* Add a line under each row? */
if (i != 0 && i % cont->ncolumns != 0)
fputs("\n&\n", fout);
fputs("\\raggedright{", fout);
latex_escaped_print(*ptr, fout);
fputc('}', fout);
if ((i + 1) % cont->ncolumns == 0)
{
fputs(" \\tabularnewline\n", fout);
if (opt_border == 3)
fputs(" \\hline\n", fout);
}
if (cancel_pressed)
break;
}
if (cont->opt->stop_table)
fputs("\\end{longtable}\n", fout);
}
static void
print_latex_vertical(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
unsigned long record = cont->opt->prior_records + 1;
1999-11-05 00:14:30 +01:00
unsigned int i;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
1999-11-05 00:14:30 +01:00
if (cancel_pressed)
return;
if (opt_border > 2)
opt_border = 2;
if (cont->opt->start_table)
{
/* print title */
if (!opt_tuples_only && cont->title)
{
fputs("\\begin{center}\n", fout);
latex_escaped_print(cont->title, fout);
fputs("\n\\end{center}\n\n", fout);
}
/* begin environment and set alignments and borders */
fputs("\\begin{tabular}{", fout);
if (opt_border == 0)
fputs("cl", fout);
else if (opt_border == 1)
fputs("c|l", fout);
else if (opt_border == 2)
fputs("|c|l|", fout);
fputs("}\n", fout);
}
1999-11-05 00:14:30 +01:00
/* print records */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
1999-11-05 00:14:30 +01:00
{
/* new record */
if (i % cont->ncolumns == 0)
1999-11-05 00:14:30 +01:00
{
if (cancel_pressed)
break;
if (!opt_tuples_only)
1999-11-05 00:14:30 +01:00
{
if (opt_border == 2)
{
1999-11-05 00:14:30 +01:00
fputs("\\hline\n", fout);
fprintf(fout, "\\multicolumn{2}{|c|}{\\textit{Record %lu}} \\\\\n", record++);
}
else
fprintf(fout, "\\multicolumn{2}{c}{\\textit{Record %lu}} \\\\\n", record++);
1999-11-05 00:14:30 +01:00
}
if (opt_border >= 1)
fputs("\\hline\n", fout);
}
latex_escaped_print(cont->headers[i % cont->ncolumns], fout);
1999-11-05 00:14:30 +01:00
fputs(" & ", fout);
latex_escaped_print(*ptr, fout);
fputs(" \\\\\n", fout);
}
if (cont->opt->stop_table)
{
if (opt_border == 2)
fputs("\\hline\n", fout);
1999-11-05 00:14:30 +01:00
fputs("\\end{tabular}\n\n\\noindent ", fout);
1999-11-05 00:14:30 +01:00
/* print footers */
if (cont->footers && !opt_tuples_only && !cancel_pressed)
1999-11-05 00:14:30 +01:00
{
printTableFooter *f;
for (f = cont->footers; f; f = f->next)
{
latex_escaped_print(f->data, fout);
fputs(" \\\\\n", fout);
}
1999-11-05 00:14:30 +01:00
}
fputc('\n', fout);
}
}
/*************************/
/* Troff -ms */
/*************************/
static void
troff_ms_escaped_print(const char *in, FILE *fout)
{
const char *p;
for (p = in; *p; p++)
switch (*p)
{
case '\\':
2005-06-09 20:40:06 +02:00
fputs("\\(rs", fout);
break;
default:
fputc(*p, fout);
}
}
static void
print_troff_ms_text(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
unsigned int i;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
if (cancel_pressed)
return;
if (opt_border > 2)
opt_border = 2;
if (cont->opt->start_table)
{
/* print title */
if (!opt_tuples_only && cont->title)
{
fputs(".LP\n.DS C\n", fout);
troff_ms_escaped_print(cont->title, fout);
fputs("\n.DE\n", fout);
}
/* begin environment and set alignments and borders */
fputs(".LP\n.TS\n", fout);
if (opt_border == 2)
fputs("center box;\n", fout);
else
fputs("center;\n", fout);
for (i = 0; i < cont->ncolumns; i++)
{
fputc(*(cont->aligns + i), fout);
if (opt_border > 0 && i < cont->ncolumns - 1)
fputs(" | ", fout);
}
fputs(".\n", fout);
/* print headers */
if (!opt_tuples_only)
{
for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
{
if (i != 0)
fputc('\t', fout);
fputs("\\fI", fout);
troff_ms_escaped_print(*ptr, fout);
fputs("\\fP", fout);
}
fputs("\n_\n", fout);
}
}
/* print cells */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
{
troff_ms_escaped_print(*ptr, fout);
if ((i + 1) % cont->ncolumns == 0)
{
fputc('\n', fout);
if (cancel_pressed)
break;
}
else
fputc('\t', fout);
}
if (cont->opt->stop_table)
{
printTableFooter *footers = footers_with_default(cont);
fputs(".TE\n.DS L\n", fout);
/* print footers */
if (footers && !opt_tuples_only && !cancel_pressed)
{
printTableFooter *f;
for (f = footers; f; f = f->next)
{
troff_ms_escaped_print(f->data, fout);
fputc('\n', fout);
}
}
fputs(".DE\n", fout);
}
}
static void
print_troff_ms_vertical(const printTableContent *cont, FILE *fout)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
unsigned long record = cont->opt->prior_records + 1;
unsigned int i;
2017-06-21 20:39:04 +02:00
const char *const *ptr;
2005-10-15 04:49:52 +02:00
unsigned short current_format = 0; /* 0=none, 1=header, 2=body */
if (cancel_pressed)
return;
if (opt_border > 2)
opt_border = 2;
if (cont->opt->start_table)
{
/* print title */
if (!opt_tuples_only && cont->title)
{
fputs(".LP\n.DS C\n", fout);
troff_ms_escaped_print(cont->title, fout);
fputs("\n.DE\n", fout);
}
/* begin environment and set alignments and borders */
fputs(".LP\n.TS\n", fout);
if (opt_border == 2)
fputs("center box;\n", fout);
else
fputs("center;\n", fout);
/* basic format */
if (opt_tuples_only)
fputs("c l;\n", fout);
}
else
current_format = 2; /* assume tuples printed already */
/* print records */
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
{
/* new record */
if (i % cont->ncolumns == 0)
{
if (cancel_pressed)
break;
if (!opt_tuples_only)
{
if (current_format != 1)
{
if (opt_border == 2 && record > 1)
fputs("_\n", fout);
if (current_format != 0)
fputs(".T&\n", fout);
fputs("c s.\n", fout);
current_format = 1;
}
fprintf(fout, "\\fIRecord %lu\\fP\n", record++);
}
if (opt_border >= 1)
fputs("_\n", fout);
}
if (!opt_tuples_only)
{
if (current_format != 2)
{
if (current_format != 0)
fputs(".T&\n", fout);
if (opt_border != 1)
fputs("c l.\n", fout);
else
fputs("c | l.\n", fout);
current_format = 2;
}
}
troff_ms_escaped_print(cont->headers[i % cont->ncolumns], fout);
fputc('\t', fout);
troff_ms_escaped_print(*ptr, fout);
fputc('\n', fout);
}
if (cont->opt->stop_table)
{
fputs(".TE\n.DS L\n", fout);
/* print footers */
if (cont->footers && !opt_tuples_only && !cancel_pressed)
{
printTableFooter *f;
for (f = cont->footers; f; f = f->next)
{
troff_ms_escaped_print(f->data, fout);
fputc('\n', fout);
}
}
fputs(".DE\n", fout);
}
}
/********************************/
/* Public functions */
/********************************/
/*
* disable_sigpipe_trap
*
* Turn off SIGPIPE interrupt --- call this before writing to a temporary
* query output file that is a pipe.
*
* No-op on Windows, where there's no SIGPIPE interrupts.
*/
void
disable_sigpipe_trap(void)
{
#ifndef WIN32
pqsignal(SIGPIPE, SIG_IGN);
#endif
}
/*
* restore_sigpipe_trap
*
* Restore normal SIGPIPE interrupt --- call this when done writing to a
* temporary query output file that was (or might have been) a pipe.
*
* Note: within psql, we enable SIGPIPE interrupts unless the permanent query
* output file is a pipe, in which case they should be kept off. This
* approach works only because psql is not currently complicated enough to
* have nested usages of short-lived output files. Otherwise we'd probably
* need a genuine save-and-restore-state approach; but for now, that would be
* useless complication. In non-psql programs, this always enables SIGPIPE.
*
* No-op on Windows, where there's no SIGPIPE interrupts.
*/
void
restore_sigpipe_trap(void)
{
#ifndef WIN32
pqsignal(SIGPIPE, always_ignore_sigpipe ? SIG_IGN : SIG_DFL);
#endif
}
/*
* set_sigpipe_trap_state
*
* Set the trap state that restore_sigpipe_trap should restore to.
*/
void
set_sigpipe_trap_state(bool ignore)
{
always_ignore_sigpipe = ignore;
}
/*
* PageOutput
*
* Tests if pager is needed and returns appropriate FILE pointer.
*
* If the topt argument is NULL no pager is used.
*/
FILE *
PageOutput(int lines, const printTableOpt *topt)
{
/* check whether we need / can / are supposed to use pager */
if (topt && topt->pager && isatty(fileno(stdin)) && isatty(fileno(stdout)))
{
#ifdef TIOCGWINSZ
unsigned short int pager = topt->pager;
int min_lines = topt->pager_min_lines;
int result;
struct winsize screen_size;
result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
/* >= accounts for a one-line prompt */
if (result == -1
|| (lines >= screen_size.ws_row && lines >= min_lines)
|| pager > 1)
#endif
{
const char *pagerprog;
FILE *pagerpipe;
pagerprog = getenv("PSQL_PAGER");
if (!pagerprog)
pagerprog = getenv("PAGER");
if (!pagerprog)
pagerprog = DEFAULT_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
else
{
/* if PAGER is empty or all-white-space, don't use pager */
if (strspn(pagerprog, " \t\r\n") == strlen(pagerprog))
return stdout;
}
disable_sigpipe_trap();
pagerpipe = popen(pagerprog, "w");
if (pagerpipe)
return pagerpipe;
/* if popen fails, silently proceed without pager */
restore_sigpipe_trap();
}
}
return stdout;
}
/*
* ClosePager
*
* Close previously opened pager pipe, if any
*/
void
ClosePager(FILE *pagerpipe)
{
if (pagerpipe && pagerpipe != stdout)
{
/*
* If printing was canceled midstream, warn about it.
*
2006-10-04 02:30:14 +02:00
* Some pagers like less use Ctrl-C as part of their command set. Even
* so, we abort our processing and warn the user what we did. If the
* pager quit as a result of the SIGINT, this message won't go
* anywhere ...
*/
if (cancel_pressed)
fprintf(pagerpipe, _("Interrupted\n"));
pclose(pagerpipe);
restore_sigpipe_trap();
}
}
/*
* Initialise a table contents struct.
* Must be called before any other printTable method is used.
*
* The title is not duplicated; the caller must ensure that the buffer
* is available for the lifetime of the printTableContent struct.
*
* If you call this, you must call printTableCleanup once you're done with the
* table.
*/
void
printTableInit(printTableContent *const content, const printTableOpt *opt,
const char *title, const int ncolumns, const int nrows)
{
content->opt = opt;
content->title = title;
content->ncolumns = ncolumns;
content->nrows = nrows;
content->headers = pg_malloc0((ncolumns + 1) * sizeof(*content->headers));
content->cells = pg_malloc0((ncolumns * nrows + 1) * sizeof(*content->cells));
content->cellmustfree = NULL;
content->footers = NULL;
content->aligns = pg_malloc0((ncolumns + 1) * sizeof(*content->align));
content->header = content->headers;
content->cell = content->cells;
content->footer = content->footers;
content->align = content->aligns;
content->cellsadded = 0;
}
/*
* Add a header to the table.
*
* Headers are not duplicated; you must ensure that the header string is
* available for the lifetime of the printTableContent struct.
*
* If translate is true, the function will pass the header through gettext.
* Otherwise, the header will not be translated.
*
* align is either 'l' or 'r', and specifies the alignment for cells in this
* column.
*/
void
printTableAddHeader(printTableContent *const content, char *header,
const bool translate, const char align)
{
#ifndef ENABLE_NLS
(void) translate; /* unused parameter */
#endif
if (content->header >= content->headers + content->ncolumns)
{
fprintf(stderr, _("Cannot add header to table content: "
"column count of %d exceeded.\n"),
content->ncolumns);
exit(EXIT_FAILURE);
}
*content->header = (char *) mbvalidate((unsigned char *) header,
content->opt->encoding);
#ifdef ENABLE_NLS
if (translate)
*content->header = _(*content->header);
#endif
content->header++;
*content->align = align;
content->align++;
}
/*
* Add a cell to the table.
*
* Cells are not duplicated; you must ensure that the cell string is available
* for the lifetime of the printTableContent struct.
*
* If translate is true, the function will pass the cell through gettext.
* Otherwise, the cell will not be translated.
*
* If mustfree is true, the cell string is freed by printTableCleanup().
* Note: Automatic freeing of translatable strings is not supported.
*/
void
printTableAddCell(printTableContent *const content, char *cell,
const bool translate, const bool mustfree)
{
#ifndef ENABLE_NLS
(void) translate; /* unused parameter */
#endif
if (content->cellsadded >= content->ncolumns * content->nrows)
{
fprintf(stderr, _("Cannot add cell to table content: "
"total cell count of %d exceeded.\n"),
content->ncolumns * content->nrows);
exit(EXIT_FAILURE);
}
*content->cell = (char *) mbvalidate((unsigned char *) cell,
content->opt->encoding);
#ifdef ENABLE_NLS
if (translate)
*content->cell = _(*content->cell);
#endif
if (mustfree)
{
if (content->cellmustfree == NULL)
content->cellmustfree =
pg_malloc0((content->ncolumns * content->nrows + 1) * sizeof(bool));
content->cellmustfree[content->cellsadded] = true;
}
content->cell++;
content->cellsadded++;
}
/*
* Add a footer to the table.
*
* Footers are added as elements of a singly-linked list, and the content is
* strdup'd, so there is no need to keep the original footer string around.
*
* Footers are never translated by the function. If you want the footer
* translated you must do so yourself, before calling printTableAddFooter. The
* reason this works differently to headers and cells is that footers tend to
* be made of up individually translated components, rather than being
* translated as a whole.
*/
void
printTableAddFooter(printTableContent *const content, const char *footer)
{
printTableFooter *f;
f = pg_malloc0(sizeof(*f));
f->data = pg_strdup(footer);
if (content->footers == NULL)
content->footers = f;
else
content->footer->next = f;
content->footer = f;
}
/*
* Change the content of the last-added footer.
*
* The current contents of the last-added footer are freed, and replaced by the
* content given in *footer. If there was no previous footer, add a new one.
*
* The content is strdup'd, so there is no need to keep the original string
* around.
*/
void
printTableSetFooter(printTableContent *const content, const char *footer)
{
if (content->footers != NULL)
{
free(content->footer->data);
content->footer->data = pg_strdup(footer);
}
else
printTableAddFooter(content, footer);
}
/*
* Free all memory allocated to this struct.
*
* Once this has been called, the struct is unusable unless you pass it to
* printTableInit() again.
*/
void
printTableCleanup(printTableContent *const content)
{
if (content->cellmustfree)
{
2010-07-06 21:19:02 +02:00
int i;
for (i = 0; i < content->nrows * content->ncolumns; i++)
{
if (content->cellmustfree[i])
free(unconstify(char *, content->cells[i]));
}
free(content->cellmustfree);
content->cellmustfree = NULL;
}
free(content->headers);
free(content->cells);
free(content->aligns);
content->opt = NULL;
content->title = NULL;
content->headers = NULL;
content->cells = NULL;
content->aligns = NULL;
content->header = NULL;
content->cell = NULL;
content->align = NULL;
if (content->footers)
{
for (content->footer = content->footers; content->footer;)
{
printTableFooter *f;
f = content->footer;
content->footer = f->next;
free(f->data);
free(f);
}
}
content->footers = NULL;
content->footer = NULL;
}
/*
* IsPagerNeeded
*
* Setup pager if required
*/
static void
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
FILE **fout, bool *is_pager)
{
if (*fout == stdout)
1999-11-05 00:14:30 +01:00
{
int lines;
if (expanded)
lines = (cont->ncolumns + 1) * cont->nrows;
1999-11-05 00:14:30 +01:00
else
lines = cont->nrows + 1;
if (!cont->opt->tuples_only)
{
printTableFooter *f;
/*
* FIXME -- this is slightly bogus: it counts the number of
* footers, not the number of lines in them.
*/
for (f = cont->footers; f; f = f->next)
lines++;
}
*fout = PageOutput(lines + extra_lines, cont->opt);
*is_pager = (*fout != stdout);
1999-11-05 00:14:30 +01:00
}
else
*is_pager = false;
}
/*
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
* Use this to print any table in the supported formats.
*
* cont: table data and formatting options
* fout: where to print to
* is_pager: true if caller has already redirected fout to be a pager pipe
* flog: if not null, also print the table there (for --log-file option)
*/
void
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
printTable(const printTableContent *cont,
FILE *fout, bool is_pager, FILE *flog)
{
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
bool is_local_pager = false;
if (cancel_pressed)
return;
if (cont->opt->format == PRINT_NOTHING)
return;
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
/* print_aligned_*() handle the pager themselves */
if (!is_pager &&
cont->opt->format != PRINT_ALIGNED &&
cont->opt->format != PRINT_WRAPPED)
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
{
IsPagerNeeded(cont, 0, (cont->opt->expanded == 1), &fout, &is_pager);
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
is_local_pager = is_pager;
}
1999-11-05 00:14:30 +01:00
/* print the stuff */
if (flog)
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
print_aligned_text(cont, flog, false);
switch (cont->opt->format)
1999-11-05 00:14:30 +01:00
{
case PRINT_UNALIGNED:
if (cont->opt->expanded == 1)
print_unaligned_vertical(cont, fout);
1999-11-05 00:14:30 +01:00
else
print_unaligned_text(cont, fout);
1999-11-05 00:14:30 +01:00
break;
case PRINT_ALIGNED:
case PRINT_WRAPPED:
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
/*
* In expanded-auto mode, force vertical if a pager is passed in;
* else we may make different decisions for different hunks of the
* query result.
*/
if (cont->opt->expanded == 1 ||
(cont->opt->expanded == 2 && is_pager))
print_aligned_vertical(cont, fout, is_pager);
1999-11-05 00:14:30 +01:00
else
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
print_aligned_text(cont, fout, is_pager);
1999-11-05 00:14:30 +01:00
break;
case PRINT_CSV:
if (cont->opt->expanded == 1)
print_csv_vertical(cont, fout);
else
print_csv_text(cont, fout);
break;
1999-11-05 00:14:30 +01:00
case PRINT_HTML:
if (cont->opt->expanded == 1)
print_html_vertical(cont, fout);
1999-11-05 00:14:30 +01:00
else
print_html_text(cont, fout);
1999-11-05 00:14:30 +01:00
break;
case PRINT_ASCIIDOC:
if (cont->opt->expanded == 1)
print_asciidoc_vertical(cont, fout);
else
print_asciidoc_text(cont, fout);
break;
1999-11-05 00:14:30 +01:00
case PRINT_LATEX:
if (cont->opt->expanded == 1)
print_latex_vertical(cont, fout);
1999-11-05 00:14:30 +01:00
else
print_latex_text(cont, fout);
1999-11-05 00:14:30 +01:00
break;
case PRINT_LATEX_LONGTABLE:
if (cont->opt->expanded == 1)
print_latex_vertical(cont, fout);
else
print_latex_longtable_text(cont, fout);
break;
case PRINT_TROFF_MS:
if (cont->opt->expanded == 1)
print_troff_ms_vertical(cont, fout);
else
print_troff_ms_text(cont, fout);
break;
1999-11-05 00:14:30 +01:00
default:
fprintf(stderr, _("invalid output format (internal error): %d"),
cont->opt->format);
exit(EXIT_FAILURE);
1999-11-05 00:14:30 +01:00
}
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
if (is_local_pager)
ClosePager(fout);
}
/*
* Use this to print query results
*
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
* result: result of a successful query
* opt: formatting options
* fout: where to print to
* is_pager: true if caller has already redirected fout to be a pager pipe
* flog: if not null, also print the data there (for --log-file option)
*/
void
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
printQuery(const PGresult *result, const printQueryOpt *opt,
FILE *fout, bool is_pager, FILE *flog)
{
printTableContent cont;
int i,
r,
c;
1999-11-05 00:14:30 +01:00
if (cancel_pressed)
return;
printTableInit(&cont, &opt->topt, opt->title,
PQnfields(result), PQntuples(result));
/* Assert caller supplied enough translate_columns[] entries */
Assert(opt->translate_columns == NULL ||
opt->n_translate_columns >= cont.ncolumns);
for (i = 0; i < cont.ncolumns; i++)
1999-11-05 00:14:30 +01:00
{
printTableAddHeader(&cont, PQfname(result, i),
opt->translate_header,
column_type_alignment(PQftype(result, i)));
1999-11-05 00:14:30 +01:00
}
/* set cells */
for (r = 0; r < cont.nrows; r++)
{
for (c = 0; c < cont.ncolumns; c++)
{
char *cell;
bool mustfree = false;
bool translate;
if (PQgetisnull(result, r, c))
cell = opt->nullPrint ? opt->nullPrint : "";
else
{
cell = PQgetvalue(result, r, c);
if (cont.aligns[c] == 'r' && opt->topt.numericLocale)
{
cell = format_numeric_locale(cell);
mustfree = true;
}
}
translate = (opt->translate_columns && opt->translate_columns[c]);
printTableAddCell(&cont, cell, translate, mustfree);
}
}
/* set footers */
if (opt->footers)
{
char **footer;
for (footer = opt->footers; *footer; footer++)
printTableAddFooter(&cont, *footer);
}
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
printTable(&cont, fout, is_pager, flog);
printTableCleanup(&cont);
}
char
column_type_alignment(Oid ftype)
{
char align;
switch (ftype)
{
case INT2OID:
case INT4OID:
case INT8OID:
case FLOAT4OID:
case FLOAT8OID:
case NUMERICOID:
case OIDOID:
case XIDOID:
case CIDOID:
case CASHOID:
align = 'r';
break;
default:
align = 'l';
break;
}
return align;
}
void
setDecimalLocale(void)
{
struct lconv *extlconv;
extlconv = localeconv();
/* Don't accept an empty decimal_point string */
if (*extlconv->decimal_point)
decimal_point = pg_strdup(extlconv->decimal_point);
else
decimal_point = "."; /* SQL output standard */
/*
* Although the Open Group standard allows locales to supply more than one
* group width, we consider only the first one, and we ignore any attempt
* to suppress grouping by specifying CHAR_MAX. As in the backend's
* cash.c, we must apply a range check to avoid being fooled by variant
* CHAR_MAX values.
*/
groupdigits = *extlconv->grouping;
if (groupdigits <= 0 || groupdigits > 6)
groupdigits = 3; /* most common */
/* Don't accept an empty thousands_sep string, either */
/* similar code exists in formatting.c */
if (*extlconv->thousands_sep)
thousands_sep = pg_strdup(extlconv->thousands_sep);
/* Make sure thousands separator doesn't match decimal point symbol. */
else if (strcmp(decimal_point, ",") != 0)
thousands_sep = ",";
else
thousands_sep = ".";
}
/* get selected or default line style */
const printTextFormat *
get_line_style(const printTableOpt *opt)
{
/*
2010-02-26 03:01:40 +01:00
* Note: this function mainly exists to preserve the convention that a
* printTableOpt struct can be initialized to zeroes to get default
* behavior.
*/
if (opt->line_style != NULL)
return opt->line_style;
else
return &pg_asciiformat;
}
void
refresh_utf8format(const printTableOpt *opt)
{
printTextFormat *popt = &pg_utf8format;
const unicodeStyleBorderFormat *border;
const unicodeStyleRowFormat *header;
const unicodeStyleColumnFormat *column;
popt->name = "unicode";
border = &unicode_style.border_style[opt->unicode_border_linestyle];
header = &unicode_style.row_style[opt->unicode_header_linestyle];
column = &unicode_style.column_style[opt->unicode_column_linestyle];
popt->lrule[PRINT_RULE_TOP].hrule = border->horizontal;
popt->lrule[PRINT_RULE_TOP].leftvrule = border->down_and_right;
popt->lrule[PRINT_RULE_TOP].midvrule = column->down_and_horizontal[opt->unicode_border_linestyle];
popt->lrule[PRINT_RULE_TOP].rightvrule = border->down_and_left;
popt->lrule[PRINT_RULE_MIDDLE].hrule = header->horizontal;
popt->lrule[PRINT_RULE_MIDDLE].leftvrule = header->vertical_and_right[opt->unicode_border_linestyle];
popt->lrule[PRINT_RULE_MIDDLE].midvrule = column->vertical_and_horizontal[opt->unicode_header_linestyle];
popt->lrule[PRINT_RULE_MIDDLE].rightvrule = header->vertical_and_left[opt->unicode_border_linestyle];
popt->lrule[PRINT_RULE_BOTTOM].hrule = border->horizontal;
popt->lrule[PRINT_RULE_BOTTOM].leftvrule = border->up_and_right;
popt->lrule[PRINT_RULE_BOTTOM].midvrule = column->up_and_horizontal[opt->unicode_border_linestyle];
popt->lrule[PRINT_RULE_BOTTOM].rightvrule = border->left_and_right;
/* N/A */
popt->lrule[PRINT_RULE_DATA].hrule = "";
popt->lrule[PRINT_RULE_DATA].leftvrule = border->vertical;
popt->lrule[PRINT_RULE_DATA].midvrule = column->vertical;
popt->lrule[PRINT_RULE_DATA].rightvrule = border->vertical;
popt->midvrule_nl = column->vertical;
popt->midvrule_wrap = column->vertical;
popt->midvrule_blank = column->vertical;
/* Same for all unicode today */
popt->header_nl_left = unicode_style.header_nl_left;
popt->header_nl_right = unicode_style.header_nl_right;
popt->nl_left = unicode_style.nl_left;
popt->nl_right = unicode_style.nl_right;
popt->wrap_left = unicode_style.wrap_left;
popt->wrap_right = unicode_style.wrap_right;
popt->wrap_right_border = unicode_style.wrap_right_border;
return;
}
/*
* Compute the byte distance to the end of the string or *target_width
* display character positions, whichever comes first. Update *target_width
* to be the number of display character positions actually filled.
*/
static int
strlen_max_width(unsigned char *str, int *target_width, int encoding)
{
unsigned char *start = str;
unsigned char *end = str + strlen((char *) str);
int curr_width = 0;
while (str < end)
{
int char_width = PQdsplen((char *) str, encoding);
/*
* If the display width of the new character causes the string to
* exceed its target width, skip it and return. However, if this is
* the first character of the string (curr_width == 0), we have to
* accept it.
*/
if (*target_width < curr_width + char_width && curr_width != 0)
break;
curr_width += char_width;
str += PQmblen((char *) str, encoding);
}
*target_width = curr_width;
return str - start;
}