2016-03-24 23:27:28 +01:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
*
|
2019-01-02 18:44:25 +01:00
|
|
|
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
|
2016-03-24 23:27:28 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
2000-01-19 00:30:24 +01:00
|
|
|
*
|
2016-03-24 23:27:28 +01:00
|
|
|
* src/fe_utils/print.c
|
2000-01-19 00:30:24 +01:00
|
|
|
*
|
2016-03-24 23:27:28 +01:00
|
|
|
*-------------------------------------------------------------------------
|
2000-01-19 00:30:24 +01:00
|
|
|
*/
|
2008-03-27 04:57:34 +01:00
|
|
|
#include "postgres_fe.h"
|
|
|
|
|
2008-05-16 20:35:38 +02:00
|
|
|
#include <limits.h>
|
1999-11-04 22:56:02 +01:00
|
|
|
#include <math.h>
|
|
|
|
#include <signal.h>
|
2003-03-18 23:15:44 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#ifndef WIN32
|
|
|
|
#include <sys/ioctl.h> /* for ioctl() */
|
|
|
|
#endif
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2003-08-14 20:49:42 +02:00
|
|
|
#ifdef HAVE_TERMIOS_H
|
|
|
|
#include <termios.h>
|
|
|
|
#endif
|
|
|
|
|
2018-04-08 19:59:52 +02:00
|
|
|
#include "catalog/pg_type_d.h"
|
2016-03-24 23:27:28 +01:00
|
|
|
#include "fe_utils/mbprint.h"
|
2019-10-23 06:08:53 +02:00
|
|
|
#include "fe_utils/print.h"
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
/*
|
2016-03-24 23:27:28 +01:00
|
|
|
* If the calling program doesn't have any mechanism for setting
|
|
|
|
* cancel_pressed, it will have no effect.
|
2006-06-14 18:49:03 +02:00
|
|
|
*
|
|
|
|
* 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;
|
|
|
|
|
2015-12-03 20:28:58 +01:00
|
|
|
static bool always_ignore_sigpipe = false;
|
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* info for locale-aware numeric formatting; set up by setDecimalLocale() */
|
2005-07-14 10:42:37 +02:00
|
|
|
static char *decimal_point;
|
2015-09-25 05:01:04 +02:00
|
|
|
static int groupdigits;
|
2005-07-14 10:42:37 +02:00
|
|
|
static char *thousands_sep;
|
|
|
|
|
2012-06-10 21:20:04 +02:00
|
|
|
static char default_footer[100];
|
|
|
|
static printTableFooter default_footer_cell = {default_footer, NULL};
|
2012-05-01 22:03:45 +02:00
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
/* Line style control structures */
|
|
|
|
const printTextFormat pg_asciiformat =
|
|
|
|
{
|
|
|
|
"ascii",
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"", "|", "|", "|"}
|
2009-10-13 23:04:01 +02:00
|
|
|
},
|
2009-11-22 06:20:41 +01:00
|
|
|
"|",
|
|
|
|
"|",
|
|
|
|
"|",
|
|
|
|
" ",
|
|
|
|
"+",
|
|
|
|
" ",
|
|
|
|
"+",
|
|
|
|
".",
|
|
|
|
".",
|
|
|
|
true
|
|
|
|
};
|
|
|
|
|
|
|
|
const printTextFormat pg_asciiformat_old =
|
|
|
|
{
|
|
|
|
"old-ascii",
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"", "|", "|", "|"}
|
2009-11-22 06:20:41 +01:00
|
|
|
},
|
2009-10-13 23:04:01 +02:00
|
|
|
":",
|
|
|
|
";",
|
2009-11-22 06:20:41 +01:00
|
|
|
" ",
|
|
|
|
"+",
|
|
|
|
" ",
|
|
|
|
" ",
|
|
|
|
" ",
|
|
|
|
" ",
|
|
|
|
" ",
|
|
|
|
false
|
2009-10-13 23:04:01 +02:00
|
|
|
};
|
|
|
|
|
2014-09-12 18:04:37 +02:00
|
|
|
/* Default unicode linestyle format */
|
2016-03-13 00:16:24 +01:00
|
|
|
printTextFormat pg_utf8format;
|
2014-09-12 18:04:37 +02:00
|
|
|
|
2015-05-24 03:35:49 +02:00
|
|
|
typedef struct unicodeStyleRowFormat
|
|
|
|
{
|
2014-09-12 18:04:37 +02:00
|
|
|
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
|
|
|
|
{
|
2014-09-12 18:04:37 +02:00
|
|
|
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
|
|
|
|
{
|
2014-09-12 18:04:37 +02:00
|
|
|
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
|
|
|
|
{
|
2014-09-12 18:04:37 +02:00
|
|
|
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;
|
2014-09-12 18:04:37 +02:00
|
|
|
} unicodeStyleFormat;
|
|
|
|
|
2016-03-24 23:27:28 +01:00
|
|
|
static const unicodeStyleFormat unicode_style = {
|
2014-09-12 18:04:37 +02:00
|
|
|
{
|
|
|
|
{
|
|
|
|
/* ─ */
|
|
|
|
"\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"},
|
2009-10-13 23:04:01 +02:00
|
|
|
},
|
2009-11-22 06:20:41 +01:00
|
|
|
" ",
|
2015-05-24 03:35:49 +02:00
|
|
|
"\342\206\265", /* ↵ */
|
2009-11-22 06:20:41 +01:00
|
|
|
" ",
|
2015-05-24 03:35:49 +02:00
|
|
|
"\342\206\265", /* ↵ */
|
|
|
|
"\342\200\246", /* … */
|
|
|
|
"\342\200\246", /* … */
|
2009-11-22 06:20:41 +01:00
|
|
|
true
|
2009-10-13 23:04:01 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2008-05-10 05:31:58 +02:00
|
|
|
/* Local functions */
|
2009-06-11 16:49:15 +02:00
|
|
|
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,
|
2019-05-22 19:04:48 +02:00
|
|
|
FILE **fout, bool *is_pager);
|
2008-05-10 05:31:58 +02: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
|
|
|
static void print_aligned_vertical(const printTableContent *cont,
|
2019-05-22 19:04:48 +02:00
|
|
|
FILE *fout, bool is_pager);
|
2011-11-12 16:03:10 +01:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* Count number of digits in integral part of number */
|
2005-07-10 05:46:13 +02:00
|
|
|
static int
|
2005-07-14 23:12:41 +02:00
|
|
|
integer_digits(const char *my_str)
|
2005-07-10 05:46:13 +02:00
|
|
|
{
|
2015-09-25 05:01:04 +02:00
|
|
|
/* ignoring any sign ... */
|
|
|
|
if (my_str[0] == '-' || my_str[0] == '+')
|
2005-07-10 05:46:13 +02:00
|
|
|
my_str++;
|
2015-09-25 05:01:04 +02:00
|
|
|
/* ... count initial integral digits */
|
|
|
|
return strspn(my_str, "0123456789");
|
2005-07-14 23:12:41 +02:00
|
|
|
}
|
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* Compute additional length required for locale-aware numeric output */
|
2005-07-14 23:12:41 +02:00
|
|
|
static int
|
2005-07-18 22:57:53 +02:00
|
|
|
additional_numeric_locale_len(const char *my_str)
|
2005-07-14 23:12:41 +02:00
|
|
|
{
|
2005-10-15 04:49:52 +02:00
|
|
|
int int_len = integer_digits(my_str),
|
|
|
|
len = 0;
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* Account for added thousands_sep instances */
|
|
|
|
if (int_len > groupdigits)
|
|
|
|
len += ((int_len - 1) / groupdigits) * strlen(thousands_sep);
|
2005-07-14 23:12:41 +02:00
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* Account for possible additional length of decimal_point */
|
2005-07-18 21:27:37 +02:00
|
|
|
if (strchr(my_str, '.') != NULL)
|
2015-09-25 05:01:04 +02:00
|
|
|
len += strlen(decimal_point) - 1;
|
2005-10-15 04:49:52 +02:00
|
|
|
|
2005-07-18 21:27:37 +02:00
|
|
|
return len;
|
2005-07-10 05:46:13 +02:00
|
|
|
}
|
2005-07-14 08:46:17 +02:00
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
/*
|
2015-09-25 18:20:45 +02:00
|
|
|
* Format a numeric value per current LC_NUMERIC locale setting
|
|
|
|
*
|
2009-10-13 23:04:01 +02:00
|
|
|
* Returns the appropriately formatted string in a new allocated block,
|
2015-09-25 18:20:45 +02:00
|
|
|
* caller must free.
|
|
|
|
*
|
|
|
|
* setDecimalLocale() must have been called earlier.
|
2009-10-13 23:04:01 +02:00
|
|
|
*/
|
2005-09-27 18:30:25 +02:00
|
|
|
static char *
|
|
|
|
format_numeric_locale(const char *my_str)
|
2005-07-10 05:46:13 +02:00
|
|
|
{
|
2015-09-25 18:20:45 +02:00
|
|
|
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);
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* number of digits in first thousands group */
|
|
|
|
leading_digits = int_len % groupdigits;
|
|
|
|
if (leading_digits == 0)
|
|
|
|
leading_digits = groupdigits;
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* process sign */
|
|
|
|
if (my_str[0] == '-' || my_str[0] == '+')
|
2005-09-27 18:30:25 +02:00
|
|
|
{
|
2015-09-25 05:01:04 +02:00
|
|
|
new_str[new_str_pos++] = my_str[0];
|
2005-09-27 18:30:25 +02:00
|
|
|
my_str++;
|
|
|
|
}
|
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* process integer part of number */
|
|
|
|
for (i = 0; i < int_len; i++)
|
2005-07-10 05:46:13 +02:00
|
|
|
{
|
2015-09-25 05:01:04 +02:00
|
|
|
/* Time to insert separator? */
|
|
|
|
if (i > 0 && --leading_digits == 0)
|
2005-07-10 05:46:13 +02:00
|
|
|
{
|
2015-09-25 05:01:04 +02:00
|
|
|
strcpy(&new_str[new_str_pos], thousands_sep);
|
|
|
|
new_str_pos += strlen(thousands_sep);
|
|
|
|
leading_digits = groupdigits;
|
2005-07-10 05:46:13 +02:00
|
|
|
}
|
2015-09-25 05:01:04 +02:00
|
|
|
new_str[new_str_pos++] = my_str[i];
|
|
|
|
}
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* 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
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* copy the rest (fractional digits and/or exponent, and \0 terminator) */
|
|
|
|
strcpy(&new_str[new_str_pos], &my_str[i]);
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2015-09-25 05:01:04 +02:00
|
|
|
/* 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
|
|
|
|
2005-09-27 18:30:25 +02:00
|
|
|
return new_str;
|
2005-07-10 05:46:13 +02:00
|
|
|
}
|
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
|
2010-05-08 18:39:53 +02:00
|
|
|
/*
|
|
|
|
* fputnbytes: print exactly N bytes to a file
|
|
|
|
*
|
2010-05-09 04:16:00 +02:00
|
|
|
* We avoid using %.*s here because it can misbehave if the data
|
|
|
|
* is not valid in what libc thinks is the prevailing encoding.
|
2010-05-08 18:39:53 +02:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
fputnbytes(FILE *f, const char *str, size_t n)
|
|
|
|
{
|
|
|
|
while (n-- > 0)
|
|
|
|
fputc(*str++, f);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-09 19:15:48 +01:00
|
|
|
static void
|
|
|
|
print_separator(struct separator sep, FILE *fout)
|
|
|
|
{
|
|
|
|
if (sep.separator_zero)
|
|
|
|
fputc('\000', fout);
|
|
|
|
else if (sep.separator)
|
|
|
|
fputs(sep.separator, fout);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-05-01 22:03:45 +02:00
|
|
|
/*
|
|
|
|
* Return the list of explicitly-requested footers or, when applicable, the
|
2014-05-06 18:12:18 +02:00
|
|
|
* default "(xx rows)" footer. Always omit the default footer when given
|
2012-05-01 22:03:45 +02:00
|
|
|
* 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-04 22:56:02 +01:00
|
|
|
/*************************/
|
1999-11-05 00:14:30 +01:00
|
|
|
/* Unaligned text */
|
1999-11-04 22:56:02 +01:00
|
|
|
/*************************/
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_unaligned_text(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
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;
|
2000-04-12 19:17:23 +02:00
|
|
|
bool need_recordsep = false;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2012-02-09 19:15:48 +01:00
|
|
|
{
|
|
|
|
fputs(cont->title, fout);
|
|
|
|
print_separator(cont->opt->recordSep, fout);
|
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
/* print headers */
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
for (ptr = cont->headers; *ptr; ptr++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
if (ptr != cont->headers)
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->fieldSep, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
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 */
|
2000-04-12 19:17:23 +02:00
|
|
|
need_recordsep = true;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
|
|
|
/* print cells */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
if (need_recordsep)
|
|
|
|
{
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2000-04-12 19:17:23 +02:00
|
|
|
need_recordsep = false;
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2000-04-12 19:17:23 +02:00
|
|
|
}
|
2010-03-01 21:55:45 +01:00
|
|
|
fputs(*ptr, fout);
|
2005-10-15 04:49:52 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if ((i + 1) % cont->ncolumns)
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->fieldSep, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2000-04-12 19:17:23 +02:00
|
|
|
need_recordsep = true;
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2012-05-01 22:03:45 +02:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
|
|
|
|
|
|
|
if (!opt_tuples_only && footers != NULL && !cancel_pressed)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-05-01 22:03:45 +02:00
|
|
|
for (f = footers; f; f = f->next)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
if (need_recordsep)
|
|
|
|
{
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
need_recordsep = false;
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
fputs(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
need_recordsep = true;
|
2000-04-12 19:17:23 +02:00
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
2012-06-10 21:20:04 +02:00
|
|
|
|
2012-02-09 19:15:48 +01:00
|
|
|
/*
|
|
|
|
* 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).
|
|
|
|
*/
|
2006-08-30 00:25:08 +02:00
|
|
|
if (need_recordsep)
|
2012-02-09 19:15:48 +01:00
|
|
|
{
|
|
|
|
if (cont->opt->recordSep.separator_zero)
|
|
|
|
print_separator(cont->opt->recordSep, fout);
|
|
|
|
else
|
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_unaligned_vertical(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
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;
|
2006-08-30 00:25:08 +02:00
|
|
|
bool need_recordsep = false;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
fputs(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
need_recordsep = true;
|
|
|
|
}
|
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
else
|
|
|
|
/* assume continuing printout */
|
2006-08-30 00:25:08 +02:00
|
|
|
need_recordsep = true;
|
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
/* print records */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
if (need_recordsep)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
/* record separator is 2 occurrences of recordsep in this mode */
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
need_recordsep = false;
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2000-04-12 19:17:23 +02:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
fputs(cont->headers[i % cont->ncolumns], fout);
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->fieldSep, fout);
|
2010-03-01 21:55:45 +01:00
|
|
|
fputs(*ptr, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if ((i + 1) % cont->ncolumns)
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
else
|
|
|
|
need_recordsep = true;
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
for (f = cont->footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
fputs(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
2000-04-12 19:17:23 +02:00
|
|
|
}
|
2000-01-19 00:30:24 +01:00
|
|
|
|
2012-02-09 19:15:48 +01:00
|
|
|
/* see above in print_unaligned_text() */
|
2013-02-09 06:05:27 +01:00
|
|
|
if (need_recordsep)
|
|
|
|
{
|
|
|
|
if (cont->opt->recordSep.separator_zero)
|
|
|
|
print_separator(cont->opt->recordSep, fout);
|
|
|
|
else
|
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/********************/
|
1999-11-05 00:14:30 +01:00
|
|
|
/* Aligned text */
|
1999-11-04 22:56:02 +01:00
|
|
|
/********************/
|
|
|
|
|
|
|
|
|
|
|
|
/* draw "line" */
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
_print_horizontal_line(const unsigned int ncolumns, const unsigned int *widths,
|
2009-10-13 23:04:01 +02:00
|
|
|
unsigned short border, printTextRule pos,
|
|
|
|
const printTextFormat *format,
|
|
|
|
FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2009-10-13 23:04:01 +02:00
|
|
|
const printTextLineFormat *lformat = &format->lrule[pos];
|
1999-11-05 00:14:30 +01:00
|
|
|
unsigned int i,
|
|
|
|
j;
|
|
|
|
|
|
|
|
if (border == 1)
|
2009-10-13 23:04:01 +02:00
|
|
|
fputs(lformat->hrule, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else if (border == 2)
|
2009-10-13 23:04:01 +02:00
|
|
|
fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < ncolumns; i++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
|
|
|
for (j = 0; j < widths[i]; j++)
|
2009-10-13 23:04:01 +02:00
|
|
|
fputs(lformat->hrule, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i < ncolumns - 1)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
|
|
|
if (border == 0)
|
|
|
|
fputc(' ', fout);
|
|
|
|
else
|
2009-10-13 23:04:01 +02:00
|
|
|
fprintf(fout, "%s%s%s", lformat->hrule,
|
|
|
|
lformat->midvrule, lformat->hrule);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (border == 2)
|
2009-10-13 23:04:01 +02:00
|
|
|
fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule);
|
1999-11-05 00:14:30 +01:00
|
|
|
else if (border == 1)
|
2009-10-13 23:04:01 +02:00
|
|
|
fputs(lformat->hrule, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
|
|
|
fputc('\n', fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/*
|
2008-05-10 05:31:58 +02:00
|
|
|
* Print pretty boxes around cells.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
1999-11-04 22:56:02 +01:00
|
|
|
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)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
int encoding = cont->opt->encoding;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
2009-10-13 23:04:01 +02:00
|
|
|
const printTextFormat *format = get_line_style(cont->opt);
|
|
|
|
const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA];
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2009-06-11 16:49:15 +02:00
|
|
|
unsigned int col_count = 0,
|
|
|
|
cell_count = 0;
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
unsigned int i,
|
|
|
|
j;
|
|
|
|
|
|
|
|
unsigned int *width_header,
|
|
|
|
*max_width,
|
|
|
|
*width_wrap,
|
|
|
|
*width_average;
|
2009-06-11 16:49:15 +02:00
|
|
|
unsigned int *max_nl_lines, /* value split by newlines */
|
|
|
|
*curr_nl_line,
|
|
|
|
*max_bytes;
|
2006-02-10 01:39:04 +01:00
|
|
|
unsigned char **format_buf;
|
2008-05-08 19:04:26 +02:00
|
|
|
unsigned int width_total;
|
|
|
|
unsigned int total_header_width;
|
2008-05-16 18:59:05 +02:00
|
|
|
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
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
bool *header_done; /* Have all header lines been output? */
|
|
|
|
int *bytes_output; /* Bytes output for column value */
|
2009-11-22 06:20:41 +01:00
|
|
|
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
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border > 2)
|
|
|
|
opt_border = 2;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->ncolumns > 0)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
col_count = cont->ncolumns;
|
2012-10-02 21:35:10 +02:00
|
|
|
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));
|
2002-11-01 16:12:19 +01:00
|
|
|
}
|
|
|
|
else
|
2001-10-25 07:50:21 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
width_header = NULL;
|
|
|
|
width_average = NULL;
|
|
|
|
max_width = NULL;
|
|
|
|
width_wrap = NULL;
|
|
|
|
max_nl_lines = NULL;
|
|
|
|
curr_nl_line = NULL;
|
2006-02-10 01:39:04 +01:00
|
|
|
col_lineptrs = NULL;
|
2008-05-08 19:04:26 +02:00
|
|
|
max_bytes = NULL;
|
2006-02-10 01:39:04 +01:00
|
|
|
format_buf = NULL;
|
2008-05-08 19:04:26 +02:00
|
|
|
header_done = NULL;
|
|
|
|
bytes_output = NULL;
|
2009-11-22 06:20:41 +01:00
|
|
|
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
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* scan all column headers, find maximum width and max max_nl_lines */
|
2001-10-25 07:50:21 +02:00
|
|
|
for (i = 0; i < col_count; i++)
|
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
int width,
|
|
|
|
nl_lines,
|
|
|
|
bytes_required;
|
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) cont->headers[i], strlen(cont->headers[i]),
|
2008-05-13 00:59:58 +02:00
|
|
|
encoding, &width, &nl_lines, &bytes_required);
|
2008-05-08 19:04:26 +02:00
|
|
|
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;
|
2008-05-16 18:59:05 +02:00
|
|
|
if (nl_lines > extra_row_output_lines)
|
|
|
|
extra_row_output_lines = nl_lines;
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
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
|
|
|
}
|
2008-05-16 18:59:05 +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
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* scan all cells, find maximum width, compute cell_count */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; ptr++, i++, cell_count++)
|
2001-10-25 07:50:21 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
int width,
|
|
|
|
nl_lines,
|
|
|
|
bytes_required;
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
|
2008-05-10 05:31:58 +02:00
|
|
|
&width, &nl_lines, &bytes_required);
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
2008-05-08 19:04:26 +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
|
|
|
}
|
2002-11-01 16:12:19 +01:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* If we have rows, compute average */
|
|
|
|
if (col_count != 0 && cell_count != 0)
|
|
|
|
{
|
2009-06-11 16:49:15 +02:00
|
|
|
int rows = cell_count / col_count;
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
for (i = 0; i < col_count; i++)
|
2008-05-17 19:52:14 +02:00
|
|
|
width_average[i] /= rows;
|
2008-05-08 19:04:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* adjust the total display width based on border style */
|
1999-11-05 00:14:30 +01:00
|
|
|
if (opt_border == 0)
|
2009-11-22 06:20:41 +01:00
|
|
|
width_total = col_count;
|
1999-11-05 00:14:30 +01:00
|
|
|
else if (opt_border == 1)
|
2015-03-25 02:04:10 +01:00
|
|
|
width_total = col_count * 3 - ((col_count > 0) ? 1 : 0);
|
1999-11-04 22:56:02 +01:00
|
|
|
else
|
2008-05-08 19:04:26 +02:00
|
|
|
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++)
|
2008-05-08 19:04:26 +02:00
|
|
|
{
|
|
|
|
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
|
|
|
/*
|
2008-05-08 19:04:26 +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,
|
2009-06-11 16:49:15 +02:00
|
|
|
* 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.
|
2006-02-10 01:39:04 +01:00
|
|
|
*/
|
2008-05-08 19:04:26 +02:00
|
|
|
for (i = 0; i < col_count; i++)
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
/* Add entry for ptr == NULL array termination */
|
2012-10-02 21:35:10 +02:00
|
|
|
col_lineptrs[i] = pg_malloc0((max_nl_lines[i] + 1) *
|
|
|
|
sizeof(**col_lineptrs));
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
format_buf[i] = pg_malloc(max_bytes[i] + 1);
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
col_lineptrs[i]->ptr = format_buf[i];
|
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +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];
|
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
/*
|
|
|
|
* 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)
|
2008-05-08 19:04:26 +02:00
|
|
|
{
|
2008-05-16 18:59:05 +02:00
|
|
|
if (cont->opt->env_columns > 0)
|
|
|
|
output_columns = cont->opt->env_columns;
|
2008-05-08 19:04:26 +02:00
|
|
|
#ifdef TIOCGWINSZ
|
2008-05-16 18:59:05 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
struct winsize screen_size;
|
|
|
|
|
|
|
|
if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
|
|
|
|
output_columns = screen_size.ws_col;
|
2008-05-08 19:04:26 +02:00
|
|
|
}
|
2008-05-16 18:59:05 +02:00
|
|
|
#endif
|
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
if (cont->opt->format == PRINT_WRAPPED)
|
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
/*
|
2008-05-10 05:31:58 +02:00
|
|
|
* Optional optimized word wrap. Shrink columns with a high max/avg
|
2015-05-20 18:44:46 +02:00
|
|
|
* ratio. Slightly bias against wider columns. (Increases chance a
|
2008-05-10 05:31:58 +02:00
|
|
|
* narrow column will fit in its cell.) If available columns is
|
2014-05-06 18:12:18 +02:00
|
|
|
* positive... and greater than the width of the unshrinkable column
|
2008-05-10 05:31:58 +02:00
|
|
|
* headers
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
|
|
|
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;
|
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* 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.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
|
|
|
for (i = 0; i < col_count; i++)
|
|
|
|
{
|
|
|
|
if (width_average[i] && width_wrap[i] > width_header[i])
|
|
|
|
{
|
2008-05-10 05:31:58 +02:00
|
|
|
/* Penalize wide columns by 1% of their width */
|
2009-06-11 16:49:15 +02:00
|
|
|
double ratio;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-10 05:31:58 +02:00
|
|
|
ratio = (double) width_wrap[i] / width_average[i] +
|
|
|
|
max_width[i] * 0.01;
|
2008-05-08 19:04:26 +02:00
|
|
|
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
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* Decrease width of target column by one. */
|
|
|
|
width_wrap[worst_col]--;
|
|
|
|
width_total--;
|
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2011-11-12 16:03:10 +01:00
|
|
|
/*
|
|
|
|
* If in expanded auto mode, we have now calculated the expected width, so
|
2016-03-11 14:04:01 +01:00
|
|
|
* 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.
|
2011-11-12 16:03:10 +01:00
|
|
|
*/
|
2016-03-11 14:04:01 +01:00
|
|
|
if (cont->opt->expanded == 2 && output_columns > 0 && cont->ncolumns > 1 &&
|
2011-11-12 16:03:10 +01:00
|
|
|
(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);
|
2012-03-07 22:52:15 +01:00
|
|
|
goto cleanup;
|
2011-11-12 16:03:10 +01:00
|
|
|
}
|
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
/* If we wrapped beyond the display width, use the pager */
|
2008-05-17 23:40:44 +02:00
|
|
|
if (!is_pager && fout == stdout && output_columns > 0 &&
|
2008-05-16 18:59:05 +02:00
|
|
|
(output_columns < total_header_width || output_columns < width_total))
|
|
|
|
{
|
2015-03-28 16:07:41 +01:00
|
|
|
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;
|
2008-05-16 18:59:05 +02:00
|
|
|
}
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
/* 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)
|
2008-05-16 18:59:05 +02:00
|
|
|
{
|
|
|
|
/* scan all cells, find maximum width, compute cell_count */
|
2008-05-17 19:52:14 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++)
|
2008-05-16 18:59:05 +02:00
|
|
|
{
|
|
|
|
int width,
|
|
|
|
nl_lines,
|
|
|
|
bytes_required;
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
|
2008-05-16 18:59:05 +02:00
|
|
|
&width, &nl_lines, &bytes_required);
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* A row can have both wrapping and newlines that cause it to
|
|
|
|
* display across multiple lines. We check for both cases below.
|
2008-05-16 18:59:05 +02:00
|
|
|
*/
|
2008-05-17 19:52:14 +02:00
|
|
|
if (width > 0 && width_wrap[i])
|
|
|
|
{
|
|
|
|
unsigned int extra_lines;
|
|
|
|
|
2014-11-21 18:37:09 +01:00
|
|
|
/* don't count the first line of nl_lines - it's not "extra" */
|
|
|
|
extra_lines = ((width - 1) / width_wrap[i]) + nl_lines - 1;
|
2008-05-17 19:52:14 +02:00
|
|
|
if (extra_lines > extra_row_output_lines)
|
|
|
|
extra_row_output_lines = extra_lines;
|
|
|
|
}
|
2008-05-16 18:59:05 +02:00
|
|
|
|
2008-05-17 19:52:14 +02:00
|
|
|
/* i is the current column number: increment with wrap */
|
|
|
|
if (++i >= col_count)
|
2008-05-16 18:59:05 +02:00
|
|
|
{
|
2008-05-17 19:52:14 +02:00
|
|
|
i = 0;
|
|
|
|
/* At last column of each row, add tallest column height */
|
2008-05-16 18:59:05 +02:00
|
|
|
extra_output_lines += extra_row_output_lines;
|
|
|
|
extra_row_output_lines = 0;
|
|
|
|
}
|
|
|
|
}
|
2011-11-12 16:03:10 +01:00
|
|
|
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;
|
2008-05-16 18:59:05 +02:00
|
|
|
}
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* time to output */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->title && !opt_tuples_only)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2009-06-11 16:49:15 +02:00
|
|
|
int width,
|
|
|
|
height;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) cont->title, strlen(cont->title),
|
2008-05-13 00:59:58 +02:00
|
|
|
encoding, &width, &height, NULL);
|
2008-05-08 19:04:26 +02:00
|
|
|
if (width >= width_total)
|
2008-05-13 00:59:58 +02:00
|
|
|
/* Aligned */
|
|
|
|
fprintf(fout, "%s\n", cont->title);
|
2006-08-30 00:25:08 +02:00
|
|
|
else
|
2008-05-13 00:59:58 +02:00
|
|
|
/* Centered */
|
|
|
|
fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "",
|
|
|
|
cont->title);
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print headers */
|
|
|
|
if (!opt_tuples_only)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
int more_col_wrapping;
|
|
|
|
int curr_nl_line;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2006-02-10 01:39:04 +01:00
|
|
|
if (opt_border == 2)
|
2009-10-13 23:04:01 +02:00
|
|
|
_print_horizontal_line(col_count, width_wrap, opt_border,
|
|
|
|
PRINT_RULE_TOP, format, fout);
|
2001-10-25 07:50:21 +02:00
|
|
|
|
2006-02-10 01:39:04 +01:00
|
|
|
for (i = 0; i < col_count; i++)
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcsformat((const unsigned char *) cont->headers[i],
|
2008-05-13 00:59:58 +02:00
|
|
|
strlen(cont->headers[i]), encoding,
|
|
|
|
col_lineptrs[i], max_nl_lines[i]);
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
more_col_wrapping = col_count;
|
|
|
|
curr_nl_line = 0;
|
|
|
|
memset(header_done, false, col_count * sizeof(bool));
|
|
|
|
while (more_col_wrapping)
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border == 2)
|
2009-11-22 06:20:41 +01:00
|
|
|
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
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < cont->ncolumns; i++)
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
struct lineptr *this_line = col_lineptrs[i] + curr_nl_line;
|
2009-10-13 23:04:01 +02:00
|
|
|
unsigned int nbspace;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2009-11-22 06:20:41 +01:00
|
|
|
if (opt_border != 0 ||
|
2010-11-15 03:03:48 +01:00
|
|
|
(!format->wrap_right_border && i > 0))
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(curr_nl_line ? format->header_nl_left : " ",
|
|
|
|
fout);
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
if (!header_done[i])
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
nbspace = width_wrap[i] - this_line->width;
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
/* centered */
|
|
|
|
fprintf(fout, "%-*s%s%-*s",
|
|
|
|
nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
if (!(this_line + 1)->ptr)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
more_col_wrapping--;
|
|
|
|
header_done[i] = 1;
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
|
|
|
else
|
2008-05-08 19:04:26 +02:00
|
|
|
fprintf(fout, "%*s", width_wrap[i], "");
|
2009-11-22 06:20:41 +01:00
|
|
|
|
2010-11-15 03:03:48 +01:00
|
|
|
if (opt_border != 0 || format->wrap_right_border)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(!header_done[i] ? format->header_nl_right : " ",
|
|
|
|
fout);
|
|
|
|
|
2015-03-25 02:04:10 +01:00
|
|
|
if (opt_border != 0 && col_count > 0 && i < col_count - 1)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(dformat->midvrule, fout);
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
curr_nl_line++;
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
if (opt_border == 2)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(dformat->rightvrule, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
_print_horizontal_line(col_count, width_wrap, opt_border,
|
|
|
|
PRINT_RULE_MIDDLE, format, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* print cells, one loop per row */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i += col_count, ptr += col_count)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
bool more_lines;
|
2006-06-14 18:49:03 +02:00
|
|
|
|
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/*
|
2010-03-01 21:55:45 +01:00
|
|
|
* Format each cell.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
2006-02-10 01:39:04 +01:00
|
|
|
for (j = 0; j < col_count; j++)
|
2008-05-08 19:04:26 +02:00
|
|
|
{
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcsformat((const unsigned char *) ptr[j], strlen(ptr[j]), encoding,
|
2008-05-10 05:31:58 +02:00
|
|
|
col_lineptrs[j], max_nl_lines[j]);
|
2008-05-08 19:04:26 +02:00
|
|
|
curr_nl_line[j] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(bytes_output, 0, col_count * sizeof(int));
|
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* 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.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
|
|
|
do
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
more_lines = false;
|
|
|
|
|
|
|
|
/* left border */
|
1999-11-05 00:14:30 +01:00
|
|
|
if (opt_border == 2)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(dformat->leftvrule, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* for each column */
|
2006-02-10 01:39:04 +01:00
|
|
|
for (j = 0; j < col_count; j++)
|
2005-10-15 04:49:52 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
/* We have a valid array element, so index it */
|
|
|
|
struct lineptr *this_line = &col_lineptrs[j][curr_nl_line[j]];
|
2009-06-11 16:49:15 +02:00
|
|
|
int bytes_to_output;
|
|
|
|
int chars_to_output = width_wrap[j];
|
2015-03-25 02:04:10 +01:00
|
|
|
bool finalspaces = (opt_border == 2 ||
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
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:35:54 +02:00
|
|
|
(col_count > 0 && j < col_count - 1));
|
2006-02-12 03:56:21 +01:00
|
|
|
|
2009-11-22 06:20:41 +01:00
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
if (!this_line->ptr)
|
2008-05-10 05:31:58 +02:00
|
|
|
{
|
|
|
|
/* Past newline lines so just pad for other columns */
|
|
|
|
if (finalspaces)
|
|
|
|
fprintf(fout, "%*s", chars_to_output, "");
|
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
else
|
|
|
|
{
|
2008-05-10 05:31:58 +02:00
|
|
|
/* 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);
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* 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.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
|
|
|
if (chars_to_output > width_wrap[j])
|
|
|
|
chars_to_output = width_wrap[j];
|
|
|
|
|
2009-06-11 16:49:15 +02:00
|
|
|
if (cont->aligns[j] == 'r') /* Right aligned cell */
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
/* spaces first */
|
|
|
|
fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
|
2010-05-08 18:39:53 +02:00
|
|
|
fputnbytes(fout,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
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:35:54 +02:00
|
|
|
(char *) (this_line->ptr + bytes_output[j]),
|
2010-05-08 18:39:53 +02:00
|
|
|
bytes_to_output);
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
2017-06-21 20:39:04 +02:00
|
|
|
else /* Left aligned cell */
|
2008-05-08 19:04:26 +02:00
|
|
|
{
|
|
|
|
/* spaces second */
|
2010-05-08 18:39:53 +02:00
|
|
|
fputnbytes(fout,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
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:35:54 +02:00
|
|
|
(char *) (this_line->ptr + bytes_output[j]),
|
2010-05-08 18:39:53 +02:00
|
|
|
bytes_to_output);
|
2008-05-08 19:04:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bytes_output[j] += bytes_to_output;
|
|
|
|
|
|
|
|
/* Do we have more text to wrap? */
|
2008-05-10 05:31:58 +02:00
|
|
|
if (*(this_line->ptr + bytes_output[j]) != '\0')
|
2008-05-08 19:04:26 +02:00
|
|
|
more_lines = true;
|
2006-02-10 01:39:04 +01:00
|
|
|
else
|
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
/* 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-02-10 01:39:04 +01:00
|
|
|
}
|
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2009-11-22 06:20:41 +01: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)
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2009-11-22 06:20:41 +01:00
|
|
|
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 */
|
2009-11-22 06:20:41 +01:00
|
|
|
{
|
|
|
|
if (finalspaces ||
|
|
|
|
wrap[j] == PRINT_LINE_WRAP_WRAP ||
|
2010-02-26 03:01:40 +01:00
|
|
|
wrap[j] == PRINT_LINE_WRAP_NEWLINE)
|
2009-11-22 06:20:41 +01:00
|
|
|
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);
|
2015-03-25 02:04:10 +01:00
|
|
|
else if (opt_border == 2 || (col_count > 0 && j < col_count - 1))
|
2009-11-22 06:20:41 +01:00
|
|
|
fputc(' ', fout);
|
|
|
|
|
|
|
|
/* Print column divider, if not the last column */
|
2015-03-25 02:04:10 +01:00
|
|
|
if (opt_border != 0 && (col_count > 0 && j < col_count - 1))
|
2009-11-22 06:20:41 +01:00
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
if (wrap[j + 1] == PRINT_LINE_WRAP_WRAP)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(format->midvrule_wrap, fout);
|
2010-02-26 03:01:40 +01:00
|
|
|
else if (wrap[j + 1] == PRINT_LINE_WRAP_NEWLINE)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(format->midvrule_nl, fout);
|
2009-06-11 16:49:15 +02:00
|
|
|
else if (col_lineptrs[j + 1][curr_nl_line[j + 1]].ptr == NULL)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(format->midvrule_blank, fout);
|
2006-02-10 01:39:04 +01:00
|
|
|
else
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(dformat->midvrule, fout);
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
2005-09-27 18:30:25 +02:00
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2008-05-10 05:31:58 +02:00
|
|
|
/* end-of-row border */
|
1999-11-05 00:14:30 +01:00
|
|
|
if (opt_border == 2)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(dformat->rightvrule, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
fputc('\n', fout);
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
} while (more_lines);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2012-05-01 22:03:45 +02:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border == 2 && !cancel_pressed)
|
2009-10-13 23:04:01 +02:00
|
|
|
_print_horizontal_line(col_count, width_wrap, opt_border,
|
|
|
|
PRINT_RULE_BOTTOM, format, fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2012-05-01 22:03:45 +02:00
|
|
|
if (footers && !opt_tuples_only && !cancel_pressed)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-05-01 22:03:45 +02:00
|
|
|
for (f = footers; f; f = f->next)
|
2008-05-13 00:59:58 +02:00
|
|
|
fprintf(fout, "%s\n", f->data);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2012-03-07 22:52:15 +01:00
|
|
|
cleanup:
|
1999-11-05 00:14:30 +01:00
|
|
|
/* clean up */
|
2010-01-30 19:59:51 +01:00
|
|
|
for (i = 0; i < col_count; i++)
|
|
|
|
{
|
|
|
|
free(col_lineptrs[i]);
|
|
|
|
free(format_buf[i]);
|
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
free(width_header);
|
|
|
|
free(width_average);
|
|
|
|
free(max_width);
|
|
|
|
free(width_wrap);
|
|
|
|
free(max_nl_lines);
|
|
|
|
free(curr_nl_line);
|
2006-02-10 01:39:04 +01:00
|
|
|
free(col_lineptrs);
|
2008-05-08 19:04:26 +02:00
|
|
|
free(max_bytes);
|
2010-01-30 19:59:51 +01:00
|
|
|
free(format_buf);
|
2008-05-08 19:04:26 +02:00
|
|
|
free(header_done);
|
|
|
|
free(bytes_output);
|
2010-01-30 19:59:51 +01:00
|
|
|
free(wrap);
|
2008-05-16 18:59:05 +02: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)
|
2008-05-16 18:59:05 +02:00
|
|
|
ClosePager(fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
static void
|
2014-09-12 17:11:53 +02:00
|
|
|
print_aligned_vertical_line(const printTextFormat *format,
|
|
|
|
const unsigned short opt_border,
|
2009-10-13 23:04:01 +02:00
|
|
|
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;
|
2009-10-13 23:04:01 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
1999-11-04 22:56:02 +01:00
|
|
|
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)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
2009-10-13 23:04:01 +02:00
|
|
|
const printTextFormat *format = get_line_style(cont->opt);
|
|
|
|
const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA];
|
2008-05-13 00:59:58 +02:00
|
|
|
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,
|
2006-02-10 01:39:04 +01:00
|
|
|
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,
|
2014-08-18 13:06:11 +02:00
|
|
|
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 */
|
2006-06-14 18:49:03 +02:00
|
|
|
|
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
if (opt_border > 2)
|
|
|
|
opt_border = 2;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->cells[0] == NULL && cont->opt->start_table &&
|
|
|
|
cont->opt->stop_table)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
2015-03-25 02:04:10 +01:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
2015-03-28 00:50:55 +01:00
|
|
|
|
2015-03-25 02:04:10 +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);
|
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
return;
|
|
|
|
}
|
1999-12-10 04:59:30 +01:00
|
|
|
|
2011-11-12 16:03:10 +01:00
|
|
|
/*
|
|
|
|
* 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
|
2015-12-01 20:47:13 +01:00
|
|
|
* to recalculate the pager requirement based on vertical output.
|
2011-11-12 16:03:10 +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_pager)
|
|
|
|
{
|
|
|
|
IsPagerNeeded(cont, 0, true, &fout, &is_pager);
|
|
|
|
is_local_pager = is_pager;
|
|
|
|
}
|
2011-11-12 16:03:10 +01:00
|
|
|
|
2006-02-10 01:39:04 +01:00
|
|
|
/* Find the maximum dimensions for the headers */
|
2008-05-13 00:59:58 +02:00
|
|
|
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
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
int width,
|
|
|
|
height,
|
2006-10-04 02:30:14 +02:00
|
|
|
fs;
|
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) cont->headers[i], strlen(cont->headers[i]),
|
2008-05-13 00:59:58 +02:00
|
|
|
encoding, &width, &height, &fs);
|
2008-05-08 19:04:26 +02:00
|
|
|
if (width > hwidth)
|
|
|
|
hwidth = width;
|
2006-02-10 01:39:04 +01:00
|
|
|
if (height > hheight)
|
2014-08-18 13:06:11 +02:00
|
|
|
{
|
2006-02-10 01:39:04 +01:00
|
|
|
hheight = height;
|
2014-08-18 13:06:11 +02:00
|
|
|
hmultiline = true;
|
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
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
|
|
|
}
|
2003-04-04 17:48:38 +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 */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; ptr++, i++)
|
2001-10-25 07:50:21 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
int width,
|
|
|
|
height,
|
2006-10-04 02:30:14 +02:00
|
|
|
fs;
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
|
2008-05-10 05:31:58 +02:00
|
|
|
&width, &height, &fs);
|
2008-05-08 19:04:26 +02:00
|
|
|
if (width > dwidth)
|
|
|
|
dwidth = width;
|
2006-02-10 01:39:04 +01:00
|
|
|
if (height > dheight)
|
2014-08-18 13:06:11 +02:00
|
|
|
{
|
2006-02-10 01:39:04 +01:00
|
|
|
dheight = height;
|
2014-08-18 13:06:11 +02:00
|
|
|
dmultiline = true;
|
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
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
|
|
|
|
*/
|
2012-10-02 21:35:10 +02:00
|
|
|
dlineptr = pg_malloc((sizeof(*dlineptr)) * (dheight + 1));
|
|
|
|
hlineptr = pg_malloc((sizeof(*hlineptr)) * (hheight + 1));
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
dlineptr->ptr = pg_malloc(dformatsize);
|
|
|
|
hlineptr->ptr = pg_malloc(hformatsize);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
|
|
|
fprintf(fout, "%s\n", cont->title);
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
}
|
|
|
|
|
2015-11-30 23:53:32 +01:00
|
|
|
/*
|
|
|
|
* Calculate available width for data in wrapped mode
|
|
|
|
*/
|
2014-04-28 19:41:36 +02:00
|
|
|
if (cont->opt->format == PRINT_WRAPPED)
|
|
|
|
{
|
2015-11-30 23:53:32 +01:00
|
|
|
unsigned int swidth,
|
|
|
|
rwidth = 0,
|
|
|
|
newdwidth;
|
2014-04-28 19:41:36 +02:00
|
|
|
|
|
|
|
if (opt_border == 0)
|
2014-08-18 13:06:11 +02:00
|
|
|
{
|
|
|
|
/*
|
2015-11-30 23:53:32 +01:00
|
|
|
* 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.)
|
2014-08-18 13:06:11 +02:00
|
|
|
*/
|
|
|
|
swidth = 1;
|
2015-11-30 23:53:32 +01:00
|
|
|
|
|
|
|
/* We might need a column for header newline markers, too */
|
|
|
|
if (hmultiline)
|
|
|
|
swidth++;
|
2014-08-18 13:06:11 +02:00
|
|
|
}
|
2014-04-28 19:41:36 +02:00
|
|
|
else if (opt_border == 1)
|
|
|
|
{
|
2014-08-18 13:06:11 +02:00
|
|
|
/*
|
2015-11-30 23:53:32 +01:00
|
|
|
* For border = 1, two spaces and a vrule in the middle. (As
|
|
|
|
* above, we might need one more column for a wrap marker.)
|
2014-08-18 13:06:11 +02:00
|
|
|
*/
|
|
|
|
swidth = 3;
|
2015-11-30 23:53:32 +01:00
|
|
|
|
|
|
|
/* We might need a column for left header newline markers, too */
|
|
|
|
if (hmultiline && (format == &pg_asciiformat_old))
|
|
|
|
swidth++;
|
2014-04-28 19:41:36 +02:00
|
|
|
}
|
2014-08-18 13:06:11 +02:00
|
|
|
else
|
2015-11-30 23:53:32 +01:00
|
|
|
{
|
2014-08-18 13:06:11 +02:00
|
|
|
/*
|
2015-11-30 23:53:32 +01:00
|
|
|
* 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.)
|
2014-08-18 13:06:11 +02:00
|
|
|
*/
|
|
|
|
swidth = 7;
|
2015-11-30 23:53:32 +01:00
|
|
|
}
|
2014-04-28 19:41:36 +02:00
|
|
|
|
2015-11-30 23:53:32 +01:00
|
|
|
/* Reserve a column for data newline indicators, too, if needed */
|
|
|
|
if (dmultiline &&
|
|
|
|
opt_border < 2 && format != &pg_asciiformat_old)
|
|
|
|
swidth++;
|
2014-08-18 13:06:11 +02:00
|
|
|
|
2015-11-30 23:53:32 +01:00
|
|
|
/* Determine width required for record header lines */
|
2014-04-28 19:41:36 +02:00
|
|
|
if (!opt_tuples_only)
|
|
|
|
{
|
2015-12-01 20:47:13 +01:00
|
|
|
if (cont->nrows > 0)
|
|
|
|
rwidth = 1 + (int) log10(cont->nrows);
|
2014-04-28 19:41:36 +02:00
|
|
|
if (opt_border == 0)
|
2014-08-18 13:06:11 +02:00
|
|
|
rwidth += 9; /* "* RECORD " */
|
2014-04-28 19:41:36 +02:00
|
|
|
else if (opt_border == 1)
|
2014-08-18 13:06:11 +02:00
|
|
|
rwidth += 12; /* "-[ RECORD ]" */
|
2014-04-30 17:15:15 +02:00
|
|
|
else
|
2014-08-18 13:06:11 +02:00
|
|
|
rwidth += 15; /* "+-[ RECORD ]-+" */
|
2015-11-30 23:53:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* We might need to do the rest of the calculation twice */
|
|
|
|
for (;;)
|
|
|
|
{
|
2015-12-01 20:47:13 +01:00
|
|
|
unsigned int width;
|
2015-11-30 23:53:32 +01:00
|
|
|
|
|
|
|
/* Total width required to not wrap data */
|
|
|
|
width = hwidth + swidth + dwidth;
|
2015-12-01 20:47:13 +01:00
|
|
|
/* ... and not the header lines, either */
|
|
|
|
if (width < rwidth)
|
|
|
|
width = rwidth;
|
2014-04-28 19:41:36 +02:00
|
|
|
|
2015-12-01 20:47:13 +01:00
|
|
|
if (output_columns > 0)
|
2015-11-30 23:53:32 +01:00
|
|
|
{
|
2015-12-01 20:47:13 +01:00
|
|
|
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;
|
|
|
|
}
|
2015-11-30 23:53:32 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-12-01 20:47:13 +01:00
|
|
|
/* 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-11-30 23:53:32 +01:00
|
|
|
}
|
2015-05-24 03:35:49 +02:00
|
|
|
|
2014-08-18 13:06:11 +02:00
|
|
|
/*
|
2015-11-30 23:53:32 +01:00
|
|
|
* If we will need to wrap data and didn't already allocate a data
|
|
|
|
* newline/wrap marker column, do so and recompute.
|
2014-08-18 13:06:11 +02:00
|
|
|
*/
|
2015-11-30 23:53:32 +01:00
|
|
|
if (newdwidth < dwidth && !dmultiline &&
|
|
|
|
opt_border < 2 && format != &pg_asciiformat_old)
|
|
|
|
{
|
|
|
|
dmultiline = true;
|
|
|
|
swidth++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
2014-08-18 13:06:11 +02:00
|
|
|
}
|
2015-11-30 23:53:32 +01:00
|
|
|
|
|
|
|
dwidth = newdwidth;
|
2014-04-28 19:41:36 +02:00
|
|
|
}
|
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
/* print records */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2009-10-13 23:04:01 +02:00
|
|
|
printTextRule pos;
|
2014-04-28 19:41:36 +02:00
|
|
|
int dline,
|
|
|
|
hline,
|
2006-10-04 02:30:14 +02:00
|
|
|
dcomplete,
|
2014-04-28 19:41:36 +02:00
|
|
|
hcomplete,
|
|
|
|
offset,
|
|
|
|
chars_to_output;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (i == 0)
|
2010-02-26 03:01:40 +01:00
|
|
|
pos = PRINT_RULE_TOP;
|
2009-10-13 23:04:01 +02:00
|
|
|
else
|
|
|
|
pos = PRINT_RULE_MIDDLE;
|
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
/* Print record header (e.g. "[ RECORD N ]") above each record */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i % cont->ncolumns == 0)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2014-08-18 13:06:11 +02:00
|
|
|
unsigned int lhwidth = hwidth;
|
2015-05-24 03:35:49 +02:00
|
|
|
|
2014-08-18 13:06:11 +02:00
|
|
|
if ((opt_border < 2) &&
|
2014-08-22 03:37:41 +02:00
|
|
|
(hmultiline) &&
|
2014-08-18 13:06:11 +02:00
|
|
|
(format == &pg_asciiformat_old))
|
2015-05-24 03:35:49 +02:00
|
|
|
lhwidth++; /* for newline indicators */
|
2014-08-18 13:06:11 +02:00
|
|
|
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
2014-09-12 17:11:53 +02:00
|
|
|
print_aligned_vertical_line(format, opt_border, record++,
|
|
|
|
lhwidth, dwidth, pos, fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
else if (i != 0 || !cont->opt->start_table || opt_border == 2)
|
2014-09-12 17:11:53 +02:00
|
|
|
print_aligned_vertical_line(format, opt_border, 0, lhwidth,
|
|
|
|
dwidth, pos, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
* Includes tab completion. It's not magic, but it's very cool. At any
rate
it's better than what used to be there.
* Does proper SQL "host variable" substitution as pointed out by Andreas
Zeugwetter (thanks): select * from :foo; Also some changes in how ':'
and ';' are treated (escape with \ to send to backend). This does
_not_
affect the '::' cast operator, but perhaps others that contain : or ;
(but there are none right now).
* To show description with a <something> listing, append '?' to command
name, e.g., \df?. This seemed to be the convenient and logical
solution.
Or append a '+' to see more useless information, e.g., \df+.
* Fixed fflush()'ing bug pointed out by Jan during the regression test
discussion.
* Added LastOid variable. This ought to take care of TODO item "Add a
function to return the last inserted oid, for use in psql scripts"
(under CLIENTS)
E.g.,
insert into foo values(...);
insert into bar values(..., :LastOid);
\echo $LastOid
* \d command shows constraints, rules, and triggers defined on the table
(in addition to indices)
* Various fixes, optimizations, corrections
* Documentation update as well
Note: This now requires snprintf(), which, if necessary, is taken from
src/backend/port. This is certainly a little weird, but it should
suffice
until a source tree cleanup is done.
Enjoy.
--
Peter Eisentraut Sernanders väg 10:115
1999-11-26 05:24:17 +01:00
|
|
|
|
2006-02-10 01:39:04 +01:00
|
|
|
/* Format the header */
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcsformat((const unsigned char *) cont->headers[i % cont->ncolumns],
|
2008-05-13 00:59:58 +02:00
|
|
|
strlen(cont->headers[i % cont->ncolumns]),
|
2008-05-10 05:31:58 +02:00
|
|
|
encoding, hlineptr, hheight);
|
2006-02-10 01:39:04 +01:00
|
|
|
/* Format the data */
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcsformat((const unsigned char *) *ptr, strlen(*ptr), encoding,
|
2008-05-10 05:31:58 +02:00
|
|
|
dlineptr, dheight);
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2014-05-06 18:12:18 +02:00
|
|
|
/*
|
|
|
|
* Loop through header and data in parallel dealing with newlines and
|
|
|
|
* wrapped lines until they're both exhausted
|
|
|
|
*/
|
2014-04-28 19:41:36 +02:00
|
|
|
dline = hline = 0;
|
2006-02-10 01:39:04 +01:00
|
|
|
dcomplete = hcomplete = 0;
|
2014-04-28 19:41:36 +02:00
|
|
|
offset = 0;
|
|
|
|
chars_to_output = dlineptr[dline].width;
|
2006-02-10 01:39:04 +01:00
|
|
|
while (!dcomplete || !hcomplete)
|
2005-07-10 05:46:13 +02:00
|
|
|
{
|
2014-04-28 19:41:36 +02:00
|
|
|
/* Left border */
|
2006-02-10 01:39:04 +01:00
|
|
|
if (opt_border == 2)
|
2014-04-28 19:41:36 +02:00
|
|
|
fprintf(fout, "%s", dformat->leftvrule);
|
|
|
|
|
|
|
|
/* Header (never wrapped so just need to deal with newlines) */
|
2006-02-10 01:39:04 +01:00
|
|
|
if (!hcomplete)
|
|
|
|
{
|
2014-08-18 13:06:11 +02:00
|
|
|
int swidth = hwidth,
|
|
|
|
target_width = hwidth;
|
2015-05-24 03:35:49 +02:00
|
|
|
|
2014-08-18 13:06:11 +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
|
|
|
|
2014-08-18 13:06:11 +02:00
|
|
|
/*
|
|
|
|
* Header text
|
|
|
|
*/
|
|
|
|
strlen_max_width(hlineptr[hline].ptr, &target_width,
|
2014-04-28 19:41:36 +02:00
|
|
|
encoding);
|
|
|
|
fprintf(fout, "%-s", hlineptr[hline].ptr);
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2014-08-18 13:06:11 +02:00
|
|
|
/*
|
|
|
|
* Spacer
|
|
|
|
*/
|
|
|
|
swidth -= target_width;
|
|
|
|
if (swidth > 0)
|
2014-04-28 19:41:36 +02:00
|
|
|
fprintf(fout, "%*s", swidth, " ");
|
|
|
|
|
2014-08-22 03:37:41 +02:00
|
|
|
/*
|
2014-08-18 13:06:11 +02:00
|
|
|
* New line indicator or separator's space
|
|
|
|
*/
|
2014-04-28 19:41:36 +02:00
|
|
|
if (hlineptr[hline + 1].ptr)
|
|
|
|
{
|
|
|
|
/* More lines after this one due to a newline */
|
2014-08-18 13:06:11 +02:00
|
|
|
if ((opt_border > 0) ||
|
|
|
|
(hmultiline && (format != &pg_asciiformat_old)))
|
|
|
|
fputs(format->header_nl_right, fout);
|
2014-04-28 19:41:36 +02:00
|
|
|
hline++;
|
2014-04-30 03:35:07 +02:00
|
|
|
}
|
|
|
|
else
|
2014-04-28 19:41:36 +02:00
|
|
|
{
|
|
|
|
/* This was the last line of the header */
|
2014-08-18 13:06:11 +02:00
|
|
|
if ((opt_border > 0) ||
|
|
|
|
(hmultiline && (format != &pg_asciiformat_old)))
|
|
|
|
fputs(" ", fout);
|
2006-02-10 01:39:04 +01:00
|
|
|
hcomplete = 1;
|
2014-04-28 19:41:36 +02:00
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
2005-07-10 05:46:13 +02:00
|
|
|
else
|
2014-04-28 19:41:36 +02:00
|
|
|
{
|
2014-08-18 13:06:11 +02:00
|
|
|
unsigned int swidth = hwidth + opt_border;
|
2015-05-24 03:35:49 +02:00
|
|
|
|
2014-08-18 13:06:11 +02:00
|
|
|
if ((opt_border < 2) &&
|
|
|
|
(hmultiline) &&
|
|
|
|
(format == &pg_asciiformat_old))
|
|
|
|
swidth++;
|
|
|
|
|
2014-08-22 03:37:41 +02:00
|
|
|
if ((opt_border == 0) &&
|
2014-08-18 13:06:11 +02:00
|
|
|
(format != &pg_asciiformat_old) &&
|
|
|
|
(hmultiline))
|
|
|
|
swidth++;
|
|
|
|
|
|
|
|
fprintf(fout, "%*s", swidth, " ");
|
2014-04-28 19:41:36 +02:00
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
/* Separator */
|
2006-02-10 01:39:04 +01:00
|
|
|
if (opt_border > 0)
|
2014-04-28 19:41:36 +02:00
|
|
|
{
|
|
|
|
if (offset)
|
|
|
|
fputs(format->midvrule_wrap, fout);
|
2015-11-30 23:53:32 +01:00
|
|
|
else if (dline == 0)
|
2014-04-28 19:41:36 +02:00
|
|
|
fputs(dformat->midvrule, fout);
|
|
|
|
else
|
2015-11-30 23:53:32 +01:00
|
|
|
fputs(format->midvrule_nl, fout);
|
2014-04-28 19:41:36 +02:00
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
/* Data */
|
2006-02-10 01:39:04 +01:00
|
|
|
if (!dcomplete)
|
|
|
|
{
|
2014-08-18 13:06:11 +02:00
|
|
|
int target_width = dwidth,
|
2014-05-06 18:12:18 +02:00
|
|
|
bytes_to_output,
|
2014-08-18 13:06:11 +02:00
|
|
|
swidth = dwidth;
|
2014-04-28 19:41:36 +02:00
|
|
|
|
2014-08-18 13:06:11 +02:00
|
|
|
/*
|
2015-11-30 23:53:32 +01:00
|
|
|
* Left spacer or wrap indicator
|
2014-08-18 13:06:11 +02:00
|
|
|
*/
|
2015-11-30 23:53:32 +01:00
|
|
|
fputs(offset == 0 ? " " : format->wrap_left, fout);
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2014-08-18 13:06:11 +02:00
|
|
|
/*
|
|
|
|
* Data text
|
|
|
|
*/
|
2014-04-28 19:41:36 +02:00
|
|
|
bytes_to_output = strlen_max_width(dlineptr[dline].ptr + offset,
|
|
|
|
&target_width, encoding);
|
2014-05-06 18:12:18 +02:00
|
|
|
fputnbytes(fout, (char *) (dlineptr[dline].ptr + offset),
|
2014-04-28 19:41:36 +02:00
|
|
|
bytes_to_output);
|
|
|
|
|
|
|
|
chars_to_output -= target_width;
|
|
|
|
offset += bytes_to_output;
|
|
|
|
|
2014-08-18 13:06:11 +02:00
|
|
|
/* Spacer */
|
|
|
|
swidth -= target_width;
|
2014-04-28 19:41:36 +02:00
|
|
|
|
|
|
|
if (chars_to_output)
|
|
|
|
{
|
|
|
|
/* continuing a wrapped column */
|
2014-08-18 13:06:11 +02:00
|
|
|
if ((opt_border > 1) ||
|
|
|
|
(dmultiline && (format != &pg_asciiformat_old)))
|
|
|
|
{
|
|
|
|
if (swidth > 0)
|
|
|
|
fprintf(fout, "%*s", swidth, " ");
|
|
|
|
fputs(format->wrap_right, fout);
|
|
|
|
}
|
2014-04-28 19:41:36 +02:00
|
|
|
}
|
|
|
|
else if (dlineptr[dline + 1].ptr)
|
|
|
|
{
|
|
|
|
/* reached a newline in the column */
|
2014-08-18 13:06:11 +02:00
|
|
|
if ((opt_border > 1) ||
|
|
|
|
(dmultiline && (format != &pg_asciiformat_old)))
|
|
|
|
{
|
|
|
|
if (swidth > 0)
|
|
|
|
fprintf(fout, "%*s", swidth, " ");
|
|
|
|
fputs(format->nl_right, fout);
|
|
|
|
}
|
2014-04-28 19:41:36 +02:00
|
|
|
dline++;
|
|
|
|
offset = 0;
|
|
|
|
chars_to_output = dlineptr[dline].width;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* reached the end of the cell */
|
2014-08-18 13:06:11 +02:00
|
|
|
if (opt_border > 1)
|
|
|
|
{
|
|
|
|
if (swidth > 0)
|
|
|
|
fprintf(fout, "%*s", swidth, " ");
|
|
|
|
fputs(" ", fout);
|
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
dcomplete = 1;
|
2014-04-28 19:41:36 +02:00
|
|
|
}
|
|
|
|
|
2014-08-18 13:06:11 +02:00
|
|
|
/* Right border */
|
2014-04-28 19:41:36 +02:00
|
|
|
if (opt_border == 2)
|
|
|
|
fputs(dformat->rightvrule, fout);
|
|
|
|
|
|
|
|
fputs("\n", fout);
|
2006-10-04 02:30:14 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-05-06 18:12:18 +02:00
|
|
|
/*
|
|
|
|
* 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)
|
2014-04-28 19:41:36 +02:00
|
|
|
fputs("\n", fout);
|
2006-10-04 02:30:14 +02:00
|
|
|
else
|
2014-04-28 19:41:36 +02:00
|
|
|
fprintf(fout, "%*s %s\n", dwidth, "", dformat->rightvrule);
|
2006-10-04 02:30:14 +02:00
|
|
|
}
|
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
if (opt_border == 2 && !cancel_pressed)
|
2014-09-12 17:11:53 +02:00
|
|
|
print_aligned_vertical_line(format, opt_border, 0, hwidth, dwidth,
|
2009-10-13 23:04:01 +02:00
|
|
|
PRINT_RULE_BOTTOM, fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border < 2)
|
|
|
|
fputc('\n', fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
for (f = cont->footers; f; f = f->next)
|
|
|
|
fprintf(fout, "%s\n", f->data);
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-02-10 01:39:04 +01:00
|
|
|
free(hlineptr->ptr);
|
|
|
|
free(dlineptr->ptr);
|
|
|
|
free(hlineptr);
|
|
|
|
free(dlineptr);
|
2011-11-12 16:03:10 +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)
|
2011-11-12 16:03:10 +01:00
|
|
|
ClosePager(fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************/
|
2018-11-26 21:18:55 +01:00
|
|
|
/* 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 */
|
1999-11-04 22:56:02 +01:00
|
|
|
/**********************/
|
|
|
|
|
|
|
|
|
2003-06-12 09:52:51 +02:00
|
|
|
void
|
1999-11-05 00:14:30 +01:00
|
|
|
html_escaped_print(const char *in, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
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++)
|
2005-06-15 00:15:57 +02:00
|
|
|
{
|
1999-11-05 00:14:30 +01:00
|
|
|
switch (*p)
|
|
|
|
{
|
|
|
|
case '&':
|
|
|
|
fputs("&", fout);
|
|
|
|
break;
|
|
|
|
case '<':
|
|
|
|
fputs("<", fout);
|
|
|
|
break;
|
|
|
|
case '>':
|
|
|
|
fputs(">", fout);
|
|
|
|
break;
|
|
|
|
case '\n':
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs("<br />\n", fout);
|
|
|
|
break;
|
|
|
|
case '"':
|
|
|
|
fputs(""", fout);
|
|
|
|
break;
|
2005-06-15 00:15:57 +02:00
|
|
|
case ' ':
|
|
|
|
/* protect leading space, for EXPLAIN output */
|
|
|
|
if (leading_space)
|
|
|
|
fputs(" ", fout);
|
|
|
|
else
|
|
|
|
fputs(" ", fout);
|
|
|
|
break;
|
1999-11-05 00:14:30 +01:00
|
|
|
default:
|
|
|
|
fputc(*p, fout);
|
|
|
|
}
|
2005-06-15 00:15:57 +02:00
|
|
|
if (*p != ' ')
|
|
|
|
leading_space = false;
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_html_text(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
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
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
fprintf(fout, "<table border=\"%d\"", opt_border);
|
|
|
|
if (opt_table_attr)
|
|
|
|
fprintf(fout, " %s", opt_table_attr);
|
|
|
|
fputs(">\n", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(" <caption>", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
html_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("</caption>\n", fout);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* print headers */
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" <tr>\n", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
for (ptr = cont->headers; *ptr; ptr++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
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 */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i % cont->ncolumns == 0)
|
2006-06-14 18:49:03 +02:00
|
|
|
{
|
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs(" <tr valign=\"top\">\n", fout);
|
2006-06-14 18:49:03 +02:00
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
fprintf(fout, " <td align=\"%s\">", cont->aligns[(i) % cont->ncolumns] == 'r' ? "right" : "left");
|
2005-07-10 05:46:13 +02:00
|
|
|
/* is string only whitespace? */
|
2005-10-15 04:49:52 +02:00
|
|
|
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs(" ", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
|
|
|
html_escaped_print(*ptr, fout);
|
2005-07-10 05:46:13 +02:00
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs("</td>\n", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if ((i + 1) % cont->ncolumns == 0)
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs(" </tr>\n", fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2003-06-12 09:52:51 +02:00
|
|
|
{
|
2012-05-01 22:03:45 +02:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("</table>\n", fout);
|
|
|
|
|
|
|
|
/* print footers */
|
2012-05-01 22:03:45 +02:00
|
|
|
if (!opt_tuples_only && footers != NULL && !cancel_pressed)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("<p>", fout);
|
2012-05-01 22:03:45 +02:00
|
|
|
for (f = footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
html_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("<br />\n", fout);
|
|
|
|
}
|
|
|
|
fputs("</p>", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
fputc('\n', fout);
|
2003-06-12 09:52:51 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_html_vertical(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
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
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fprintf(fout, "<table border=\"%d\"", opt_border);
|
|
|
|
if (opt_table_attr)
|
|
|
|
fprintf(fout, " %s", opt_table_attr);
|
|
|
|
fputs(">\n", fout);
|
|
|
|
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(" <caption>", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
html_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("</caption>\n", fout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
/* print records */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i % cont->ncolumns == 0)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
2006-08-30 00:25:08 +02:00
|
|
|
fprintf(fout,
|
|
|
|
"\n <tr><td colspan=\"2\" align=\"center\">Record %lu</td></tr>\n",
|
|
|
|
record++);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs("\n <tr><td colspan=\"2\"> </td></tr>\n", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs(" <tr valign=\"top\">\n"
|
1999-11-05 00:14:30 +01:00
|
|
|
" <th>", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
html_escaped_print(cont->headers[i % cont->ncolumns], fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs("</th>\n", fout);
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
fprintf(fout, " <td align=\"%s\">", cont->aligns[i % cont->ncolumns] == 'r' ? "right" : "left");
|
2005-07-10 05:46:13 +02:00
|
|
|
/* is string only whitespace? */
|
2005-10-15 04:49:52 +02:00
|
|
|
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs(" ", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
|
|
|
html_escaped_print(*ptr, fout);
|
2005-07-10 05:46:13 +02:00
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs("</td>\n </tr>\n", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2003-06-12 09:52:51 +02:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("</table>\n", fout);
|
|
|
|
|
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("<p>", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
for (f = cont->footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
html_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("<br />\n", fout);
|
|
|
|
}
|
|
|
|
fputs("</p>", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
fputc('\n', fout);
|
2003-06-12 09:52:51 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-03-31 17:33:25 +02:00
|
|
|
/*************************/
|
2018-11-26 21:18:55 +01:00
|
|
|
/* ASCIIDOC */
|
2015-03-31 17:33:25 +02:00
|
|
|
/*************************/
|
|
|
|
|
2018-11-26 21:18:55 +01:00
|
|
|
|
2015-03-31 17:33:25 +02:00
|
|
|
static void
|
|
|
|
asciidoc_escaped_print(const char *in, FILE *fout)
|
|
|
|
{
|
|
|
|
const char *p;
|
2015-05-24 03:35:49 +02:00
|
|
|
|
2015-03-31 17:33:25 +02:00
|
|
|
for (p = in; *p; p++)
|
|
|
|
{
|
2015-05-24 03:35:49 +02:00
|
|
|
switch (*p)
|
2015-03-31 17:33:25 +02:00
|
|
|
{
|
|
|
|
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;
|
2015-03-31 17:33:25 +02:00
|
|
|
|
|
|
|
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++)
|
2015-03-31 17:33:25 +02:00
|
|
|
{
|
|
|
|
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;
|
2015-03-31 17:33:25 +02:00
|
|
|
|
|
|
|
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;
|
2015-03-31 17:33:25 +02:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-26 21:18:55 +01:00
|
|
|
|
1999-11-04 22:56:02 +01:00
|
|
|
/*************************/
|
2006-08-30 00:25:08 +02:00
|
|
|
/* LaTeX */
|
1999-11-04 22:56:02 +01:00
|
|
|
/*************************/
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
1999-11-05 00:14:30 +01:00
|
|
|
latex_escaped_print(const char *in, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
1999-11-05 00:14:30 +01:00
|
|
|
const char *p;
|
|
|
|
|
|
|
|
for (p = in; *p; p++)
|
|
|
|
switch (*p)
|
|
|
|
{
|
2018-11-26 23:32:51 +01:00
|
|
|
/*
|
|
|
|
* 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;
|
2018-11-26 23:32:51 +01:00
|
|
|
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;
|
2004-08-06 20:09:15 +02:00
|
|
|
case '_':
|
|
|
|
fputs("\\_", fout);
|
|
|
|
break;
|
1999-11-05 00:14:30 +01:00
|
|
|
case '{':
|
|
|
|
fputs("\\{", fout);
|
|
|
|
break;
|
2018-11-26 23:32:51 +01:00
|
|
|
case '|':
|
|
|
|
fputs("\\textbar{}", fout);
|
|
|
|
break;
|
1999-11-05 00:14:30 +01:00
|
|
|
case '}':
|
|
|
|
fputs("\\}", fout);
|
|
|
|
break;
|
2018-11-26 23:32:51 +01:00
|
|
|
case '~':
|
|
|
|
fputs("\\~{}", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
break;
|
|
|
|
case '\n':
|
2018-11-26 23:32:51 +01:00
|
|
|
/* 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);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_latex_text(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
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
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2013-01-17 17:39:38 +01:00
|
|
|
if (opt_border > 3)
|
|
|
|
opt_border = 3;
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs("\\begin{center}\n", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
latex_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\n\\end{center}\n\n", fout);
|
|
|
|
}
|
2004-08-06 20:09:15 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* begin environment and set alignments and borders */
|
|
|
|
fputs("\\begin{tabular}{", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2013-01-17 17:39:38 +01:00
|
|
|
if (opt_border >= 2)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("| ", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < cont->ncolumns; i++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
fputc(*(cont->aligns + i), fout);
|
|
|
|
if (opt_border != 0 && i < cont->ncolumns - 1)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" | ", fout);
|
|
|
|
}
|
2013-01-17 17:39:38 +01:00
|
|
|
if (opt_border >= 2)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" |", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("}\n", fout);
|
|
|
|
|
2013-01-17 17:39:38 +01:00
|
|
|
if (!opt_tuples_only && opt_border >= 2)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\\hline\n", fout);
|
|
|
|
|
|
|
|
/* print headers */
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
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 */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2010-03-01 21:55:45 +01:00
|
|
|
latex_escaped_print(*ptr, fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if ((i + 1) % cont->ncolumns == 0)
|
2006-06-14 18:49:03 +02:00
|
|
|
{
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs(" \\\\\n", fout);
|
2013-01-17 17:39:38 +01:00
|
|
|
if (opt_border == 3)
|
|
|
|
fputs("\\hline\n", fout);
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
|
|
|
fputs(" & ", fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2012-05-01 22:03:45 +02:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
|
|
|
|
2013-01-18 14:30:31 +01:00
|
|
|
if (opt_border == 2)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\\hline\n", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\\end{tabular}\n\n\\noindent ", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2012-05-01 22:03:45 +02:00
|
|
|
if (footers && !opt_tuples_only && !cancel_pressed)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-05-01 22:03:45 +02:00
|
|
|
for (f = footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
latex_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" \\\\\n", fout);
|
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-11-26 21:18:55 +01:00
|
|
|
/*************************/
|
|
|
|
/* LaTeX longtable */
|
|
|
|
/*************************/
|
|
|
|
|
|
|
|
|
2013-01-17 17:39:38 +01:00
|
|
|
static void
|
2013-01-18 20:02:53 +01:00
|
|
|
print_latex_longtable_text(const printTableContent *cont, FILE *fout)
|
2013-01-17 17:39:38 +01:00
|
|
|
{
|
|
|
|
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;
|
2013-01-17 17:39:38 +01:00
|
|
|
|
|
|
|
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,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
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:35:54 +02:00
|
|
|
LONGTABLE_WHITESPACE), 1, fout);
|
2013-01-17 17:39:38 +01:00
|
|
|
last_opt_table_attr_char = next_opt_table_attr_char;
|
|
|
|
next_opt_table_attr_char += strcspn(next_opt_table_attr_char,
|
2013-05-29 22:58:43 +02:00
|
|
|
LONGTABLE_WHITESPACE);
|
2013-01-17 17:39:38 +01:00
|
|
|
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,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
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:35:54 +02:00
|
|
|
LONGTABLE_WHITESPACE), 1, fout);
|
2013-01-17 17:39:38 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
1999-11-04 22:56:02 +01:00
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_latex_vertical(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
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
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border > 2)
|
|
|
|
opt_border = 2;
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs("\\begin{center}\n", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
latex_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
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-04 22:56:02 +01:00
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
/* print records */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
|
|
|
/* new record */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i % cont->ncolumns == 0)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
|
|
|
if (opt_border == 2)
|
2004-08-06 20:09:15 +02:00
|
|
|
{
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs("\\hline\n", fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fprintf(fout, "\\multicolumn{2}{|c|}{\\textit{Record %lu}} \\\\\n", record++);
|
2004-08-06 20:09:15 +02:00
|
|
|
}
|
|
|
|
else
|
2006-08-30 00:25:08 +02:00
|
|
|
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);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
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);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
if (opt_border == 2)
|
|
|
|
fputs("\\hline\n", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\\end{tabular}\n\n\\noindent ", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->footers && !opt_tuples_only && !cancel_pressed)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
|
|
|
for (f = cont->footers; f; f = f->next)
|
2005-07-14 09:32:01 +02:00
|
|
|
{
|
2010-03-01 21:55:45 +01:00
|
|
|
latex_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" \\\\\n", fout);
|
2005-07-14 09:32:01 +02:00
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-06-09 17:27:27 +02:00
|
|
|
/*************************/
|
2018-11-26 21:18:55 +01:00
|
|
|
/* Troff -ms */
|
2005-06-09 17:27:27 +02:00
|
|
|
/*************************/
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
2005-06-09 17:27:27 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fputc(*p, fout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_troff_ms_text(const printTableContent *cont, FILE *fout)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
2005-06-09 17:27:27 +02:00
|
|
|
unsigned int i;
|
2017-06-21 20:39:04 +02:00
|
|
|
const char *const *ptr;
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border > 2)
|
|
|
|
opt_border = 2;
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(".LP\n.DS C\n", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
troff_ms_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\n.DE\n", fout);
|
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* 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);
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < cont->ncolumns; i++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
fputc(*(cont->aligns + i), fout);
|
|
|
|
if (opt_border > 0 && i < cont->ncolumns - 1)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" | ", fout);
|
|
|
|
}
|
|
|
|
fputs(".\n", fout);
|
|
|
|
|
|
|
|
/* print headers */
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
if (i != 0)
|
|
|
|
fputc('\t', fout);
|
|
|
|
fputs("\\fI", fout);
|
|
|
|
troff_ms_escaped_print(*ptr, fout);
|
|
|
|
fputs("\\fP", fout);
|
|
|
|
}
|
|
|
|
fputs("\n_\n", fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* print cells */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2010-03-01 21:55:45 +01:00
|
|
|
troff_ms_escaped_print(*ptr, fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if ((i + 1) % cont->ncolumns == 0)
|
2006-06-14 18:49:03 +02:00
|
|
|
{
|
2005-06-09 17:27:27 +02:00
|
|
|
fputc('\n', fout);
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
else
|
|
|
|
fputc('\t', fout);
|
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2012-05-01 22:03:45 +02:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(".TE\n.DS L\n", fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2012-05-01 22:03:45 +02:00
|
|
|
if (footers && !opt_tuples_only && !cancel_pressed)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-05-01 22:03:45 +02:00
|
|
|
for (f = footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
troff_ms_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(".DE\n", fout);
|
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_troff_ms_vertical(const printTableContent *cont, FILE *fout)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
|
|
|
unsigned long record = cont->opt->prior_records + 1;
|
2005-06-09 17:27:27 +02:00
|
|
|
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 */
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border > 2)
|
|
|
|
opt_border = 2;
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(".LP\n.DS C\n", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
troff_ms_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
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 */
|
|
|
|
|
2005-06-09 17:27:27 +02:00
|
|
|
/* print records */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
|
|
|
/* new record */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i % cont->ncolumns == 0)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
|
|
|
if (current_format != 1)
|
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border == 2 && record > 1)
|
2005-06-09 17:27:27 +02:00
|
|
|
fputs("_\n", fout);
|
|
|
|
if (current_format != 0)
|
|
|
|
fputs(".T&\n", fout);
|
|
|
|
fputs("c s.\n", fout);
|
|
|
|
current_format = 1;
|
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
fprintf(fout, "\\fIRecord %lu\\fP\n", record++);
|
2005-06-09 17:27:27 +02:00
|
|
|
}
|
|
|
|
if (opt_border >= 1)
|
|
|
|
fputs("_\n", fout);
|
|
|
|
}
|
|
|
|
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
troff_ms_escaped_print(cont->headers[i % cont->ncolumns], fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
fputc('\t', fout);
|
2010-03-01 21:55:45 +01:00
|
|
|
troff_ms_escaped_print(*ptr, fout);
|
2005-07-14 09:32:01 +02:00
|
|
|
|
2005-06-09 17:27:27 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(".TE\n.DS L\n", fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->footers && !opt_tuples_only && !cancel_pressed)
|
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
|
|
|
for (f = cont->footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
troff_ms_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(".DE\n", fout);
|
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
1999-11-04 22:56:02 +01:00
|
|
|
/********************************/
|
2015-12-03 20:28:58 +01:00
|
|
|
/* Public functions */
|
1999-11-04 22:56:02 +01:00
|
|
|
/********************************/
|
|
|
|
|
|
|
|
|
2015-12-03 20:28:58 +01:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-03-18 23:15:44 +01:00
|
|
|
/*
|
|
|
|
* PageOutput
|
|
|
|
*
|
|
|
|
* Tests if pager is needed and returns appropriate FILE pointer.
|
2015-03-28 16:07:41 +01:00
|
|
|
*
|
|
|
|
* If the topt argument is NULL no pager is used.
|
2003-03-18 23:15:44 +01:00
|
|
|
*/
|
|
|
|
FILE *
|
2015-03-28 16:07:41 +01:00
|
|
|
PageOutput(int lines, const printTableOpt *topt)
|
2003-03-18 23:15:44 +01:00
|
|
|
{
|
|
|
|
/* check whether we need / can / are supposed to use pager */
|
2015-03-28 16:07:41 +01:00
|
|
|
if (topt && topt->pager && isatty(fileno(stdin)) && isatty(fileno(stdout)))
|
2003-03-18 23:15:44 +01:00
|
|
|
{
|
|
|
|
#ifdef TIOCGWINSZ
|
2015-09-29 00:42:30 +02:00
|
|
|
unsigned short int pager = topt->pager;
|
|
|
|
int min_lines = topt->pager_min_lines;
|
2003-03-18 23:15:44 +01:00
|
|
|
int result;
|
|
|
|
struct winsize screen_size;
|
|
|
|
|
|
|
|
result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
|
2003-07-25 23:48:45 +02:00
|
|
|
|
|
|
|
/* >= accounts for a one-line prompt */
|
2015-03-28 16:07:41 +01:00
|
|
|
if (result == -1
|
|
|
|
|| (lines >= screen_size.ws_row && lines >= min_lines)
|
|
|
|
|| pager > 1)
|
2003-03-18 23:15:44 +01:00
|
|
|
#endif
|
2015-12-03 20:28:58 +01:00
|
|
|
{
|
|
|
|
const char *pagerprog;
|
|
|
|
FILE *pagerpipe;
|
|
|
|
|
2017-09-05 18:02:06 +02:00
|
|
|
pagerprog = getenv("PSQL_PAGER");
|
|
|
|
if (!pagerprog)
|
|
|
|
pagerprog = getenv("PAGER");
|
2003-03-18 23:15:44 +01:00
|
|
|
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;
|
|
|
|
}
|
2015-12-03 20:28:58 +01:00
|
|
|
disable_sigpipe_trap();
|
2006-08-30 00:25:08 +02:00
|
|
|
pagerpipe = popen(pagerprog, "w");
|
|
|
|
if (pagerpipe)
|
|
|
|
return pagerpipe;
|
2016-12-07 18:39:24 +01:00
|
|
|
/* if popen fails, silently proceed without pager */
|
|
|
|
restore_sigpipe_trap();
|
2003-03-18 23:15:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return stdout;
|
|
|
|
}
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/*
|
|
|
|
* 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 ...
|
2006-08-30 00:25:08 +02:00
|
|
|
*/
|
|
|
|
if (cancel_pressed)
|
|
|
|
fprintf(pagerpipe, _("Interrupted\n"));
|
|
|
|
|
|
|
|
pclose(pagerpipe);
|
2015-12-03 20:28:58 +01:00
|
|
|
restore_sigpipe_trap();
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
|
|
|
}
|
2003-03-18 23:15:44 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
/*
|
|
|
|
* Initialise a table contents struct.
|
2008-05-13 02:14:11 +02:00
|
|
|
* Must be called before any other printTable method is used.
|
2008-05-13 00:59:58 +02:00
|
|
|
*
|
2008-05-13 02:14:11 +02:00
|
|
|
* The title is not duplicated; the caller must ensure that the buffer
|
|
|
|
* is available for the lifetime of the printTableContent struct.
|
2008-05-13 00:59:58 +02:00
|
|
|
*
|
|
|
|
* If you call this, you must call printTableCleanup once you're done with the
|
|
|
|
* table.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
printTableInit(printTableContent *const content, const printTableOpt *opt,
|
2008-05-21 18:00:10 +02:00
|
|
|
const char *title, const int ncolumns, const int nrows)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
content->opt = opt;
|
|
|
|
content->title = title;
|
|
|
|
content->ncolumns = ncolumns;
|
|
|
|
content->nrows = nrows;
|
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
content->headers = pg_malloc0((ncolumns + 1) * sizeof(*content->headers));
|
2008-05-13 00:59:58 +02:00
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
content->cells = pg_malloc0((ncolumns * nrows + 1) * sizeof(*content->cells));
|
2008-05-13 00:59:58 +02:00
|
|
|
|
2010-03-01 21:55:45 +01:00
|
|
|
content->cellmustfree = NULL;
|
2008-05-13 00:59:58 +02:00
|
|
|
content->footers = NULL;
|
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
content->aligns = pg_malloc0((ncolumns + 1) * sizeof(*content->align));
|
2008-05-13 00:59:58 +02:00
|
|
|
|
|
|
|
content->header = content->headers;
|
|
|
|
content->cell = content->cells;
|
|
|
|
content->footer = content->footers;
|
|
|
|
content->align = content->aligns;
|
2010-03-01 21:55:45 +01:00
|
|
|
content->cellsadded = 0;
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
2003-03-18 23:15:44 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
1999-11-04 22:56:02 +01:00
|
|
|
void
|
2012-03-16 19:03:38 +01:00
|
|
|
printTableAddHeader(printTableContent *const content, char *header,
|
2008-05-21 18:00:10 +02:00
|
|
|
const bool translate, const char align)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
#ifndef ENABLE_NLS
|
2009-06-11 16:49:15 +02:00
|
|
|
(void) translate; /* unused parameter */
|
2008-05-13 00:59:58 +02:00
|
|
|
#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.
|
2010-03-01 21:55:45 +01:00
|
|
|
*
|
|
|
|
* If mustfree is true, the cell string is freed by printTableCleanup().
|
2010-03-01 22:27:26 +01:00
|
|
|
* Note: Automatic freeing of translatable strings is not supported.
|
2008-05-13 00:59:58 +02:00
|
|
|
*/
|
|
|
|
void
|
2012-03-16 19:03:38 +01:00
|
|
|
printTableAddCell(printTableContent *const content, char *cell,
|
2010-03-01 21:55:45 +01:00
|
|
|
const bool translate, const bool mustfree)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
#ifndef ENABLE_NLS
|
2009-06-11 16:49:15 +02:00
|
|
|
(void) translate; /* unused parameter */
|
2008-05-13 00:59:58 +02:00
|
|
|
#endif
|
|
|
|
|
2010-03-01 21:55:45 +01:00
|
|
|
if (content->cellsadded >= content->ncolumns * content->nrows)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
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)
|
2010-03-01 22:27:26 +01:00
|
|
|
*content->cell = _(*content->cell);
|
2008-05-13 00:59:58 +02:00
|
|
|
#endif
|
2010-03-01 21:55:45 +01:00
|
|
|
|
|
|
|
if (mustfree)
|
|
|
|
{
|
|
|
|
if (content->cellmustfree == NULL)
|
2012-10-02 21:35:10 +02:00
|
|
|
content->cellmustfree =
|
|
|
|
pg_malloc0((content->ncolumns * content->nrows + 1) * sizeof(bool));
|
2010-03-01 21:55:45 +01:00
|
|
|
|
|
|
|
content->cellmustfree[content->cellsadded] = true;
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
content->cell++;
|
2010-03-01 21:55:45 +01:00
|
|
|
content->cellsadded++;
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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
|
2014-05-06 18:12:18 +02:00
|
|
|
* translated you must do so yourself, before calling printTableAddFooter. The
|
2008-05-13 00:59:58 +02:00
|
|
|
* 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;
|
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
f = pg_malloc0(sizeof(*f));
|
2008-05-13 00:59:58 +02:00
|
|
|
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
|
2008-05-21 18:00:10 +02:00
|
|
|
printTableCleanup(printTableContent *const content)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
2010-03-01 21:55:45 +01:00
|
|
|
if (content->cellmustfree)
|
|
|
|
{
|
2010-07-06 21:19:02 +02:00
|
|
|
int i;
|
|
|
|
|
2010-03-01 21:55:45 +01:00
|
|
|
for (i = 0; i < content->nrows * content->ncolumns; i++)
|
|
|
|
{
|
|
|
|
if (content->cellmustfree[i])
|
2019-01-29 01:16:24 +01:00
|
|
|
free(unconstify(char *, content->cells[i]));
|
2010-03-01 21:55:45 +01:00
|
|
|
}
|
|
|
|
free(content->cellmustfree);
|
|
|
|
content->cellmustfree = NULL;
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2008-05-16 18:59:05 +02:00
|
|
|
* IsPagerNeeded
|
|
|
|
*
|
|
|
|
* Setup pager if required
|
2008-05-13 00:59:58 +02:00
|
|
|
*/
|
2009-06-12 18:17:29 +02:00
|
|
|
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)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-16 18:59:05 +02:00
|
|
|
if (*fout == stdout)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
int lines;
|
|
|
|
|
2011-11-12 16:03:10 +01:00
|
|
|
if (expanded)
|
2008-05-13 00:59:58 +02:00
|
|
|
lines = (cont->ncolumns + 1) * cont->nrows;
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2008-05-13 00:59:58 +02:00
|
|
|
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)
|
2002-09-02 01:30:46 +02:00
|
|
|
lines++;
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
|
|
|
|
2015-03-28 16:07:41 +01:00
|
|
|
*fout = PageOutput(lines + extra_lines, cont->opt);
|
2008-05-16 18:59:05 +02:00
|
|
|
*is_pager = (*fout != stdout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
*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)
|
2008-05-16 18:59:05 +02:00
|
|
|
*/
|
|
|
|
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)
|
2008-05-16 18:59:05 +02: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
|
|
|
bool is_local_pager = false;
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
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 &&
|
2011-11-12 16:03:10 +01:00
|
|
|
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
|
|
|
{
|
2011-11-12 16:03:10 +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 */
|
|
|
|
|
2005-06-14 04:57:45 +02:00
|
|
|
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);
|
2005-06-14 04:57:45 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
switch (cont->opt->format)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
|
|
|
case PRINT_UNALIGNED:
|
2011-11-12 16:03:10 +01:00
|
|
|
if (cont->opt->expanded == 1)
|
2008-05-16 18:59:05 +02:00
|
|
|
print_unaligned_vertical(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
print_unaligned_text(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
break;
|
|
|
|
case PRINT_ALIGNED:
|
2008-05-08 19:04:26 +02:00
|
|
|
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;
|
2018-11-26 21:18:55 +01:00
|
|
|
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:
|
2011-11-12 16:03:10 +01:00
|
|
|
if (cont->opt->expanded == 1)
|
2008-05-16 18:59:05 +02:00
|
|
|
print_html_vertical(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
print_html_text(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
break;
|
2015-03-31 17:33:25 +02:00
|
|
|
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:
|
2011-11-12 16:03:10 +01:00
|
|
|
if (cont->opt->expanded == 1)
|
2008-05-16 18:59:05 +02:00
|
|
|
print_latex_vertical(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
print_latex_text(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
break;
|
2013-01-17 17:39:38 +01:00
|
|
|
case PRINT_LATEX_LONGTABLE:
|
|
|
|
if (cont->opt->expanded == 1)
|
|
|
|
print_latex_vertical(cont, fout);
|
|
|
|
else
|
2013-01-18 20:02:53 +01:00
|
|
|
print_latex_longtable_text(cont, fout);
|
2013-01-17 17:39:38 +01:00
|
|
|
break;
|
2005-06-09 17:27:27 +02:00
|
|
|
case PRINT_TROFF_MS:
|
2011-11-12 16:03:10 +01:00
|
|
|
if (cont->opt->expanded == 1)
|
2008-05-16 18:59:05 +02:00
|
|
|
print_troff_ms_vertical(cont, fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
print_troff_ms_text(cont, fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
break;
|
1999-11-05 00:14:30 +01:00
|
|
|
default:
|
2009-04-11 16:11:45 +02:00
|
|
|
fprintf(stderr, _("invalid output format (internal error): %d"),
|
2008-05-13 00:59:58 +02:00
|
|
|
cont->opt->format);
|
2005-06-13 08:36:22 +02:00
|
|
|
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)
|
2008-05-16 18:59:05 +02:00
|
|
|
ClosePager(fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
/*
|
|
|
|
* 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)
|
2008-05-13 00:59:58 +02:00
|
|
|
*/
|
1999-11-04 22:56:02 +01:00
|
|
|
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)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableContent cont;
|
2007-12-12 22:41:47 +01:00
|
|
|
int i,
|
|
|
|
r,
|
|
|
|
c;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableInit(&cont, &opt->topt, opt->title,
|
|
|
|
PQnfields(result), PQntuples(result));
|
1999-11-04 22:56:02 +01:00
|
|
|
|
Fix translatability markings in psql, and add defenses against future bugs.
Several previous commits have added columns to various \d queries without
updating their translate_columns[] arrays, leading to potentially incorrect
translations in NLS-enabled builds. Offenders include commit 893686762
(added prosecdef to \df+), c9ac00e6e (added description to \dc+) and
3b17efdfd (added description to \dC+). Fix those cases back to 9.3 or
9.2 as appropriate.
Since this is evidently more easily missed than one would like, in HEAD
also add an Assert that the supplied array is long enough. This requires
an API change for printQuery(), so it seems inappropriate for back
branches, but presumably all future changes will be tested in HEAD anyway.
In HEAD and 9.3, also clean up a whole lot of sloppiness in the emitted
SQL for \dy (event triggers): lack of translatability due to failing to
pass words-to-be-translated through gettext_noop(), inadequate schema
qualification, and sloppy formatting resulting in unnecessarily ugly
-E output.
Peter Eisentraut and Tom Lane, per bug #8702 from Sergey Burladyan
2014-01-04 22:05:16 +01:00
|
|
|
/* Assert caller supplied enough translate_columns[] entries */
|
|
|
|
Assert(opt->translate_columns == NULL ||
|
|
|
|
opt->n_translate_columns >= cont.ncolumns);
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < cont.ncolumns; i++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableAddHeader(&cont, PQfname(result, i),
|
2016-04-09 01:23:18 +02:00
|
|
|
opt->translate_header,
|
|
|
|
column_type_alignment(PQftype(result, i)));
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
/* set cells */
|
|
|
|
for (r = 0; r < cont.nrows; r++)
|
|
|
|
{
|
|
|
|
for (c = 0; c < cont.ncolumns; c++)
|
|
|
|
{
|
2009-06-11 16:49:15 +02:00
|
|
|
char *cell;
|
2010-03-01 21:55:45 +01:00
|
|
|
bool mustfree = false;
|
2009-06-11 16:49:15 +02:00
|
|
|
bool translate;
|
2008-05-13 00:59:58 +02:00
|
|
|
|
|
|
|
if (PQgetisnull(result, r, c))
|
|
|
|
cell = opt->nullPrint ? opt->nullPrint : "";
|
|
|
|
else
|
2010-03-01 21:55:45 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
cell = PQgetvalue(result, r, c);
|
2010-03-01 21:55:45 +01:00
|
|
|
if (cont.aligns[c] == 'r' && opt->topt.numericLocale)
|
|
|
|
{
|
|
|
|
cell = format_numeric_locale(cell);
|
|
|
|
mustfree = true;
|
|
|
|
}
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
|
2008-07-15 00:00:04 +02:00
|
|
|
translate = (opt->translate_columns && opt->translate_columns[c]);
|
2010-03-01 21:55:45 +01:00
|
|
|
printTableAddCell(&cont, cell, translate, mustfree);
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
/* set footers */
|
|
|
|
if (opt->footers)
|
|
|
|
{
|
2009-06-11 16:49:15 +02:00
|
|
|
char **footer;
|
2008-05-13 00:59:58 +02:00
|
|
|
|
|
|
|
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);
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableCleanup(&cont);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2016-04-09 01:23:18 +02:00
|
|
|
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;
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2005-07-14 10:42:37 +02:00
|
|
|
void
|
|
|
|
setDecimalLocale(void)
|
|
|
|
{
|
|
|
|
struct lconv *extlconv;
|
|
|
|
|
|
|
|
extlconv = localeconv();
|
|
|
|
|
2015-09-25 06:00:33 +02:00
|
|
|
/* Don't accept an empty decimal_point string */
|
2005-07-14 10:42:37 +02:00
|
|
|
if (*extlconv->decimal_point)
|
2008-05-13 00:59:58 +02:00
|
|
|
decimal_point = pg_strdup(extlconv->decimal_point);
|
2005-07-14 10:42:37 +02:00
|
|
|
else
|
|
|
|
decimal_point = "."; /* SQL output standard */
|
2015-09-25 05:01:04 +02:00
|
|
|
|
2015-09-25 06:00:33 +02:00
|
|
|
/*
|
|
|
|
* 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)
|
2015-09-25 05:01:04 +02:00
|
|
|
groupdigits = 3; /* most common */
|
2007-11-22 18:51:39 +01:00
|
|
|
|
2015-09-25 06:00:33 +02:00
|
|
|
/* Don't accept an empty thousands_sep string, either */
|
2007-11-22 18:51:39 +01:00
|
|
|
/* similar code exists in formatting.c */
|
2005-07-14 10:42:37 +02:00
|
|
|
if (*extlconv->thousands_sep)
|
2008-05-13 00:59:58 +02:00
|
|
|
thousands_sep = pg_strdup(extlconv->thousands_sep);
|
2007-11-22 16:10:05 +01:00
|
|
|
/* Make sure thousands separator doesn't match decimal point symbol. */
|
2007-11-21 23:28:18 +01:00
|
|
|
else if (strcmp(decimal_point, ",") != 0)
|
2005-07-14 17:54:21 +02:00
|
|
|
thousands_sep = ",";
|
2005-07-14 10:42:37 +02:00
|
|
|
else
|
2005-07-14 17:54:21 +02:00
|
|
|
thousands_sep = ".";
|
2005-07-14 10:42:37 +02:00
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
/* get selected or default line style */
|
|
|
|
const printTextFormat *
|
|
|
|
get_line_style(const printTableOpt *opt)
|
|
|
|
{
|
2009-11-25 21:26:31 +01:00
|
|
|
/*
|
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
|
2009-11-25 21:26:31 +01:00
|
|
|
* behavior.
|
|
|
|
*/
|
2009-10-13 23:04:01 +02:00
|
|
|
if (opt->line_style != NULL)
|
|
|
|
return opt->line_style;
|
|
|
|
else
|
|
|
|
return &pg_asciiformat;
|
|
|
|
}
|
|
|
|
|
2014-09-12 18:04:37 +02:00
|
|
|
void
|
|
|
|
refresh_utf8format(const printTableOpt *opt)
|
|
|
|
{
|
2016-03-13 00:16:24 +01:00
|
|
|
printTextFormat *popt = &pg_utf8format;
|
2014-09-12 18:04:37 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/*
|
2008-05-10 05:31:58 +02:00
|
|
|
* Compute the byte distance to the end of the string or *target_width
|
2014-05-06 18:12:18 +02:00
|
|
|
* display character positions, whichever comes first. Update *target_width
|
2008-05-10 05:31:58 +02:00
|
|
|
* to be the number of display character positions actually filled.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
|
|
|
static int
|
|
|
|
strlen_max_width(unsigned char *str, int *target_width, int encoding)
|
|
|
|
{
|
|
|
|
unsigned char *start = str;
|
2008-05-10 05:31:58 +02:00
|
|
|
unsigned char *end = str + strlen((char *) str);
|
2009-06-11 16:49:15 +02:00
|
|
|
int curr_width = 0;
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2008-05-10 05:31:58 +02:00
|
|
|
while (str < end)
|
2008-05-08 19:04:26 +02:00
|
|
|
{
|
2009-06-11 16:49:15 +02:00
|
|
|
int char_width = PQdsplen((char *) str, encoding);
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* 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.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
2008-05-10 05:31:58 +02:00
|
|
|
if (*target_width < curr_width + char_width && curr_width != 0)
|
2008-05-08 19:04:26 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
curr_width += char_width;
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-10 05:31:58 +02:00
|
|
|
str += PQmblen((char *) str, encoding);
|
2008-05-08 19:04:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
*target_width = curr_width;
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
return str - start;
|
|
|
|
}
|