Unix Technical Forum

Re: thousands comma numeric formatting in psql

This is a discussion on Re: thousands comma numeric formatting in psql within the Pgsql Patches forums, part of the PostgreSQL category; --> Eugen Nedelcu wrote: > Regarding locale aproach, it is trivial to replace langinfo with > localeconv: > > struct ...


Go Back   Unix Technical Forum > Database Server Software > PostgreSQL > Pgsql Patches

FAQ Members List Calendar Search Today's Posts Mark Forums Read
  #1 (permalink)  
Old 04-18-2008, 12:40 AM
Bruce Momjian
 
Posts: n/a
Default Re: thousands comma numeric formatting in psql

Eugen Nedelcu wrote:
> Regarding locale aproach, it is trivial to replace langinfo with
> localeconv:
>
> struct lconv *l = localeconv();
> char *dec_point = l->decimal_point;
>
> instead of:
>
> #include langinfo.h
> char *dec_point = nl_langinfo(__DECIMAL_POINT);
>
> I used langinfo because in linux libc it is considered
> "The Elegant and Fast Way" of using locale and conforms with
> X/Open portability guide that every modern Unix follows
> (Solaris, Linux, latest FreeBSD).
>
> Regarding locale vs. \pset numericsep, for me it doesn't matter
> which one is used. I consider the patch very usefull, and I think
> that other guys that use psql daily for working with financial data
> will appreciate it too.
>
> With a quick setting like \pset numericsep ',' all my numeric fields
> will appear in easy readable form. I must underline that to_char is
> a backend function and we talk here about a little psql feature which
> makes life a little easier (for me at least).


OK, I have applied the following patch to make numerisep a boolean, made
it locale-aware, set defaults if the locale doesn't return meaningful
values, added code to handle locale-reported groupings, and updated the
documentation.

The only question I have is whether those locale values are single-byte
strings in all locals, or could they be multi-byte or multi-character,
in which case I have to treat them as strings. For now, I added a code
comment that we treat them as single-byte strings.

--
Bruce Momjian | http://candle.pha.pa.us
pgman@candle.pha.pa.us | (610) 359-1001
+ If your life is a hard drive, | 13 Roberts Road
+ Christ can be your backup. | Newtown Square, Pennsylvania 19073

Index: doc/src/sgml/ref/psql-ref.sgml
================================================== =================
RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v
retrieving revision 1.146
retrieving revision 1.147
diff -c -r1.146 -r1.147
*** doc/src/sgml/ref/psql-ref.sgml 10 Jul 2005 03:46:12 -0000 1.146
--- doc/src/sgml/ref/psql-ref.sgml 14 Jul 2005 08:42:36 -0000 1.147
***************
*** 1,5 ****
<!--
! $PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.146 2005/07/10 03:46:12 momjian Exp $
PostgreSQL documentation
-->

--- 1,5 ----
<!--
! $PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.147 2005/07/14 08:42:36 momjian Exp $
PostgreSQL documentation
-->

***************
*** 1496,1505 ****
<term><literal>numericsep</literal></term>
<listitem>
<para>
! Specifies the character separator between groups of three digits
! to the left of the decimal marker. The default is <literal>''</>
! (none). Setting this to a period also changes the decimal marker
! to a comma.
</para>
</listitem>
</varlistentry>
--- 1496,1504 ----
<term><literal>numericsep</literal></term>
<listitem>
<para>
! Toggles the display of a locale-aware character to separate groups
! of digits to the left of the decimal marker. It also enables
! a locale-aware decimal marker.
</para>
</listitem>
</varlistentry>
Index: src/bin/psql/command.c
================================================== =================
RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v
retrieving revision 1.148
retrieving revision 1.149
diff -c -r1.148 -r1.149
*** src/bin/psql/command.c 14 Jul 2005 06:49:58 -0000 1.148
--- src/bin/psql/command.c 14 Jul 2005 08:42:37 -0000 1.149
***************
*** 3,9 ****
*
* Copyright (c) 2000-2005, PostgreSQL Global Development Group
*
! * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.148 2005/07/14 06:49:58 momjian Exp $
*/
#include "postgres_fe.h"
#include "command.h"
--- 3,9 ----
*
* Copyright (c) 2000-2005, PostgreSQL Global Development Group
*
! * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.149 2005/07/14 08:42:37 momjian Exp $
*/
#include "postgres_fe.h"
#include "command.h"
***************
*** 1420,1434 ****
: _("Expanded display is off.\n"));
}

else if (strcmp(param, "numericsep") == 0)
{
! if (value)
{
! free(popt->topt.numericSep);
! popt->topt.numericSep = pg_strdup(value);
}
- if (!quiet)
- printf(_("Numeric separator is \"%s\".\n"), popt->topt.numericSep);
}

/* null display */
--- 1420,1436 ----
: _("Expanded display is off.\n"));
}

+ /* numeric separators */
else if (strcmp(param, "numericsep") == 0)
{
! popt->topt.numericSep = !popt->topt.numericSep;
! if (!quiet)
{
! if (popt->topt.numericSep)
! puts(_("Showing numeric separators."));
! else
! puts(_("Numeric separators are off."));
}
}

/* null display */
Index: src/bin/psql/print.c
================================================== =================
RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v
retrieving revision 1.66
retrieving revision 1.67
diff -c -r1.66 -r1.67
*** src/bin/psql/print.c 14 Jul 2005 07:32:01 -0000 1.66
--- src/bin/psql/print.c 14 Jul 2005 08:42:37 -0000 1.67
***************
*** 3,9 ****
*
* Copyright (c) 2000-2005, PostgreSQL Global Development Group
*
! * $PostgreSQL: pgsql/src/bin/psql/print.c,v 1.66 2005/07/14 07:32:01 momjian Exp $
*/
#include "postgres_fe.h"
#include "common.h"
--- 3,9 ----
*
* Copyright (c) 2000-2005, PostgreSQL Global Development Group
*
! * $PostgreSQL: pgsql/src/bin/psql/print.c,v 1.67 2005/07/14 08:42:37 momjian Exp $
*/
#include "postgres_fe.h"
#include "common.h"
***************
*** 24,34 ****
--- 24,40 ----
#include <termios.h>
#endif

+ #include <locale.h>
+
#include "pqsignal.h"
#include "libpq-fe.h"

#include "mbprint.h"

+ static char *decimal_point;
+ static char *grouping;
+ static char *thousands_sep;
+
static void *
pg_local_malloc(size_t size)
{
***************
*** 47,52 ****
--- 53,59 ----
num_numericseps(const char *my_str)
{
int old_len, dec_len, int_len;
+ int groupdigits = atoi(grouping);

if (my_str[0] == '-')
my_str++;
***************
*** 55,64 ****
dec_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0;

int_len = old_len - dec_len;
! if (int_len % 3 != 0)
! return int_len / 3;
else
! return int_len / 3 - 1; /* no leading separator */
}

static int
--- 62,71 ----
dec_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0;

int_len = old_len - dec_len;
! if (int_len % groupdigits != 0)
! return int_len / groupdigits;
else
! return int_len / groupdigits - 1; /* no leading separator */
}

static int
***************
*** 68,84 ****
}

static void
! format_numericsep(char *my_str, char *numericsep)
{
int i, j, digits_before_sep, old_len, new_len, dec_len, int_len;
- char *dec_point;
char *new_str;
char *dec_value;
!
! if (strcmp(numericsep, ".") != 0)
! dec_point = ".";
! else
! dec_point = ",";

if (my_str[0] == '-')
my_str++;
--- 75,86 ----
}

static void
! format_numericsep(char *my_str)
{
int i, j, digits_before_sep, old_len, new_len, dec_len, int_len;
char *new_str;
char *dec_value;
! int groupdigits = atoi(grouping);

if (my_str[0] == '-')
my_str++;
***************
*** 86,94 ****
old_len = strlen(my_str);
dec_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0;
int_len = old_len - dec_len;
! digits_before_sep = int_len % 3;

! new_len = int_len + int_len / 3 + dec_len;
if (digits_before_sep == 0)
new_len--; /* no leading separator */

--- 88,96 ----
old_len = strlen(my_str);
dec_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0;
int_len = old_len - dec_len;
! digits_before_sep = int_len % groupdigits;

! new_len = int_len + int_len / groupdigits + dec_len;
if (digits_before_sep == 0)
new_len--; /* no leading separator */

***************
*** 99,105 ****
/* hit decimal point */
if (my_str[i] == '.')
{
! new_str[j] = *dec_point;
new_str[j+1] = '\0';
dec_value = strchr(my_str, '.');
strcat(new_str, ++dec_value);
--- 101,107 ----
/* hit decimal point */
if (my_str[i] == '.')
{
! new_str[j] = *decimal_point;
new_str[j+1] = '\0';
dec_value = strchr(my_str, '.');
strcat(new_str, ++dec_value);
***************
*** 115,122 ****

/* add separator? */
if (i != 0 &&
! (i - (digits_before_sep ? digits_before_sep : 3)) % 3 == 0)
! new_str[j++] = *numericsep;

new_str[j] = my_str[i];
}
--- 117,125 ----

/* add separator? */
if (i != 0 &&
! (i - (digits_before_sep ? digits_before_sep : groupdigits))
! % groupdigits == 0)
! new_str[j++] = *thousands_sep;

new_str[j] = my_str[i];
}
***************
*** 135,141 ****
const char *const *cells, const char *const *footers,
const char *opt_align, const char *opt_fieldsep,
const char *opt_recordsep, bool opt_tuples_only,
! char *opt_numericsep, FILE *fout)
{
unsigned int col_count = 0;
unsigned int i;
--- 138,144 ----
const char *const *cells, const char *const *footers,
const char *opt_align, const char *opt_fieldsep,
const char *opt_recordsep, bool opt_tuples_only,
! bool opt_numericsep, FILE *fout)
{
unsigned int col_count = 0;
unsigned int i;
***************
*** 174,186 ****
fputs(opt_recordsep, fout);
need_recordsep = false;
}
! if ((opt_align[i % col_count] == 'r') && strlen(*ptr) > 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell, opt_numericsep);
fputs(my_cell, fout);
free(my_cell);
}
--- 177,188 ----
fputs(opt_recordsep, fout);
need_recordsep = false;
}
! if (opt_align[i % col_count] == 'r' && opt_numericsep)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
fputs(my_cell, fout);
free(my_cell);
}
***************
*** 220,226 ****
const char *const *cells,
const char *const *footers, const char *opt_align,
const char *opt_fieldsep, const char *opt_recordsep,
! bool opt_tuples_only, char *opt_numericsep, FILE *fout)
{
unsigned int col_count = 0;
unsigned int i;
--- 222,228 ----
const char *const *cells,
const char *const *footers, const char *opt_align,
const char *opt_fieldsep, const char *opt_recordsep,
! bool opt_tuples_only, bool opt_numericsep, FILE *fout)
{
unsigned int col_count = 0;
unsigned int i;
***************
*** 251,263 ****

fputs(headers[i % col_count], fout);
fputs(opt_fieldsep, fout);
! if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell, opt_numericsep);
fputs(my_cell, fout);
free(my_cell);
}
--- 253,264 ----

fputs(headers[i % col_count], fout);
fputs(opt_fieldsep, fout);
! if (opt_align[i % col_count] == 'r' && opt_numericsep)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
fputs(my_cell, fout);
free(my_cell);
}
***************
*** 325,331 ****
static void
print_aligned_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
! const char *opt_align, bool opt_tuples_only, char *opt_numericsep,
unsigned short int opt_border, int encoding,
FILE *fout)
{
--- 326,332 ----
static void
print_aligned_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
! const char *opt_align, bool opt_tuples_only, bool opt_numericsep,
unsigned short int opt_border, int encoding,
FILE *fout)
{
***************
*** 394,401 ****
{
int numericseps;

! if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
numericseps = num_numericseps(*ptr);
else
numericseps = 0;
--- 395,401 ----
{
int numericseps;

! if (opt_align[i % col_count] == 'r' && opt_numericsep)
numericseps = num_numericseps(*ptr);
else
numericseps = 0;
***************
*** 480,491 ****
/* content */
if (opt_align[i % col_count] == 'r')
{
! if (strlen(*ptr) > 0 && opt_numericsep != NULL && strlen(opt_numericsep) > 0)
{
char *my_cell = pg_local_malloc(cell_w[i] + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell, opt_numericsep);
fprintf(fout, "%*s%s", widths[i % col_count] - cell_w[i], "", my_cell);
free(my_cell);
}
--- 480,491 ----
/* content */
if (opt_align[i % col_count] == 'r')
{
! if (opt_numericsep)
{
char *my_cell = pg_local_malloc(cell_w[i] + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
fprintf(fout, "%*s%s", widths[i % col_count] - cell_w[i], "", my_cell);
free(my_cell);
}
***************
*** 547,553 ****
print_aligned_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! char *opt_numericsep, unsigned short int opt_border,
int encoding, FILE *fout)
{
unsigned int col_count = 0;
--- 547,553 ----
print_aligned_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
int encoding, FILE *fout)
{
unsigned int col_count = 0;
***************
*** 612,619 ****
{
int numericseps;

! if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
numericseps = num_numericseps(*ptr);
else
numericseps = 0;
--- 612,618 ----
{
int numericseps;

! if (opt_align[i % col_count] == 'r' && opt_numericsep)
numericseps = num_numericseps(*ptr);
else
numericseps = 0;
***************
*** 696,704 ****
char *my_cell = pg_local_malloc(cell_w[i] + 1);

strcpy(my_cell, *ptr);
! if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
! format_numericsep(my_cell, opt_numericsep);
if (opt_border < 2)
puts(my_cell);
else
--- 695,702 ----
char *my_cell = pg_local_malloc(cell_w[i] + 1);

strcpy(my_cell, *ptr);
! if (opt_align[i % col_count] == 'r' && opt_numericsep)
! format_numericsep(my_cell);
if (opt_border < 2)
puts(my_cell);
else
***************
*** 785,791 ****
print_html_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! char *opt_numericsep, unsigned short int opt_border,
const char *opt_table_attr, FILE *fout)
{
unsigned int col_count = 0;
--- 783,789 ----
print_html_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
const char *opt_table_attr, FILE *fout)
{
unsigned int col_count = 0;
***************
*** 831,843 ****
/* is string only whitespace? */
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs("&nbsp; ", fout);
! else if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell, opt_numericsep);
html_escaped_print(my_cell, fout);
free(my_cell);
}
--- 829,840 ----
/* is string only whitespace? */
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs("&nbsp; ", fout);
! else if (opt_align[i % col_count] == 'r' && opt_numericsep)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
html_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 873,879 ****
print_html_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! char *opt_numericsep, unsigned short int opt_border,
const char *opt_table_attr, FILE *fout)
{
unsigned int col_count = 0;
--- 870,876 ----
print_html_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
const char *opt_table_attr, FILE *fout)
{
unsigned int col_count = 0;
***************
*** 917,929 ****
/* is string only whitespace? */
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs("&nbsp; ", fout);
! else if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell, opt_numericsep);
html_escaped_print(my_cell, fout);
free(my_cell);
}
--- 914,925 ----
/* is string only whitespace? */
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs("&nbsp; ", fout);
! else if (opt_align[i % col_count] == 'r' && opt_numericsep)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
html_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 999,1005 ****
print_latex_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! char *opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
--- 995,1001 ----
print_latex_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
***************
*** 1060,1072 ****
/* print cells */
for (i = 0, ptr = cells; *ptr; i++, ptr++)
{
! if (strlen(*ptr) != 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell, opt_numericsep);
latex_escaped_print(my_cell, fout);
free(my_cell);
}
--- 1056,1067 ----
/* print cells */
for (i = 0, ptr = cells; *ptr; i++, ptr++)
{
! if (opt_numericsep)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
latex_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 1103,1109 ****
print_latex_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! char *opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
--- 1098,1104 ----
print_latex_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
***************
*** 1174,1186 ****
if (footers && !opt_tuples_only)
for (ptr = footers; *ptr; ptr++)
{
! if (strlen(*ptr) != 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell, opt_numericsep);
latex_escaped_print(my_cell, fout);
free(my_cell);
}
--- 1169,1180 ----
if (footers && !opt_tuples_only)
for (ptr = footers; *ptr; ptr++)
{
! if (opt_numericsep)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
latex_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 1221,1227 ****
print_troff_ms_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! char *opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
--- 1215,1221 ----
print_troff_ms_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
***************
*** 1275,1287 ****
/* print cells */
for (i = 0, ptr = cells; *ptr; i++, ptr++)
{
! if (strlen(*ptr) != 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell, opt_numericsep);
troff_ms_escaped_print(my_cell, fout);
free(my_cell);
}
--- 1269,1280 ----
/* print cells */
for (i = 0, ptr = cells; *ptr; i++, ptr++)
{
! if (opt_numericsep)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
troff_ms_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 1315,1321 ****
print_troff_ms_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! char *opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
--- 1308,1314 ----
print_troff_ms_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
***************
*** 1345,1356 ****
if (opt_tuples_only)
fputs("c l;\n", fout);

-
/* count columns */
for (ptr = headers; *ptr; ptr++)
col_count++;

-
/* print records */
for (i = 0, ptr = cells; *ptr; i++, ptr++)
{
--- 1338,1347 ----
***************
*** 1390,1402 ****

troff_ms_escaped_print(headers[i % col_count], fout);
fputc('\t', fout);
! if (strlen(*ptr) != 0 &&
! opt_numericsep != NULL && strlen(opt_numericsep) > 0)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell, opt_numericsep);
troff_ms_escaped_print(my_cell, fout);
free(my_cell);
}
--- 1381,1392 ----

troff_ms_escaped_print(headers[i % col_count], fout);
fputc('\t', fout);
! if (opt_numericsep)
{
char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
troff_ms_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 1714,1717 ****
}


! /* the end */
--- 1704,1729 ----
}


! void
! setDecimalLocale(void)
! {
! struct lconv *extlconv;
!
! extlconv = localeconv();
!
! /* These are treated as single-byte strings in the code */
! if (*extlconv->decimal_point)
! decimal_point = strdup(extlconv->decimal_point);
! else
! decimal_point = "."; /* SQL output standard */
! if (*extlconv->grouping && atoi(extlconv->grouping) > 0)
! grouping = strdup(extlconv->grouping);
! else
! grouping = "3"; /* most common */
! if (*extlconv->thousands_sep)
! thousands_sep = strdup(extlconv->thousands_sep);
! else
! thousands_sep = ","; /* matches SQL standard decimal marker */
! }
!
!
Index: src/bin/psql/print.h
================================================== =================
RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v
retrieving revision 1.26
retrieving revision 1.27
diff -c -r1.26 -r1.27
*** src/bin/psql/print.h 10 Jul 2005 03:46:13 -0000 1.26
--- src/bin/psql/print.h 14 Jul 2005 08:42:37 -0000 1.27
***************
*** 3,9 ****
*
* Copyright (c) 2000-2005, PostgreSQL Global Development Group
*
! * $PostgreSQL: pgsql/src/bin/psql/print.h,v 1.26 2005/07/10 03:46:13 momjian Exp $
*/
#ifndef PRINT_H
#define PRINT_H
--- 3,9 ----
*
* Copyright (c) 2000-2005, PostgreSQL Global Development Group
*
! * $PostgreSQL: pgsql/src/bin/psql/print.h,v 1.27 2005/07/14 08:42:37 momjian Exp $
*/
#ifndef PRINT_H
#define PRINT_H
***************
*** 40,46 ****
char *fieldSep; /* field separator for unaligned text mode */
char *recordSep; /* record separator for unaligned text
* mode */
! char *numericSep; /* numeric units separator */
char *tableAttr; /* attributes for HTML <table ...> */
int encoding; /* character encoding */
bool normal_query; /* are we presenting the results of a
--- 40,47 ----
char *fieldSep; /* field separator for unaligned text mode */
char *recordSep; /* record separator for unaligned text
* mode */
! bool numericSep; /* locale-aware numeric units separator and
! * decimal marker */
char *tableAttr; /* attributes for HTML <table ...> */
int encoding; /* character encoding */
bool normal_query; /* are we presenting the results of a
***************
*** 86,91 ****
--- 87,94 ----
void printQuery(const PGresult *result, const printQueryOpt *opt,
FILE *fout, FILE *flog);

+ void setDecimalLocale(void);
+
#ifndef __CYGWIN__
#define DEFAULT_PAGER "more"
#else
Index: src/bin/psql/startup.c
================================================== =================
RCS file: /cvsroot/pgsql/src/bin/psql/startup.c,v
retrieving revision 1.118
retrieving revision 1.119
diff -c -r1.118 -r1.119
*** src/bin/psql/startup.c 21 Jun 2005 04:02:33 -0000 1.118
--- src/bin/psql/startup.c 14 Jul 2005 08:42:37 -0000 1.119
***************
*** 3,9 ****
*
* Copyright (c) 2000-2005, PostgreSQL Global Development Group
*
! * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.118 2005/06/21 04:02:33 tgl Exp $
*/
#include "postgres_fe.h"

--- 3,9 ----
*
* Copyright (c) 2000-2005, PostgreSQL Global Development Group
*
! * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.119 2005/07/14 08:42:37 momjian Exp $
*/
#include "postgres_fe.h"

***************
*** 130,135 ****
--- 130,136 ----
setvbuf(stderr, NULL, _IONBF, 0);
setup_win32_locks();
#endif
+ setDecimalLocale();
pset.cur_cmd_source = stdin;
pset.cur_cmd_interactive = false;
pset.encoding = PQenv2encoding();


---------------------------(end of broadcast)---------------------------
TIP 9: In versions below 8.0, the planner will ignore your desire to
choose an index scan if your joining column's datatypes do not
match

Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #2 (permalink)  
Old 04-18-2008, 12:40 AM
Peter Eisentraut
 
Posts: n/a
Default Re: thousands comma numeric formatting in psql

Am Donnerstag, 14. Juli 2005 10:48 schrieb Bruce Momjian:
> OK, I have applied the following patch to make numerisep a boolean, made
> it locale-aware, set defaults if the locale doesn't return meaningful
> values, added code to handle locale-reported groupings, and updated the
> documentation.


Then the name "numericsep" doesn't seem to make much sense anymore.

--
Peter Eisentraut
http://developer.postgresql.org/~petere/

---------------------------(end of broadcast)---------------------------
TIP 5: don't forget to increase your free space map settings

Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #3 (permalink)  
Old 04-18-2008, 12:40 AM
Tom Lane
 
Posts: n/a
Default Re: thousands comma numeric formatting in psql

Bruce Momjian <pgman@candle.pha.pa.us> writes:
> OK, I have applied the following patch to make numerisep a boolean,


"numericsep" is no longer even remotely reasonable as a name for the
parameter. Something like "numeric_use_locale" would be appropriate
(but probably too wordy).

> The only question I have is whether those locale values are single-byte
> strings in all locals, or could they be multi-byte or multi-character,
> in which case I have to treat them as strings.


I think you have to assume they could be strings.

regards, tom lane

---------------------------(end of broadcast)---------------------------
TIP 4: Have you searched our list archives?

http://archives.postgresql.org

Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #4 (permalink)  
Old 04-18-2008, 12:40 AM
Bruce Momjian
 
Posts: n/a
Default Re: thousands comma numeric formatting in psql

Tom Lane wrote:
> > The only question I have is whether those locale values are single-byte
> > strings in all locals, or could they be multi-byte or multi-character,
> > in which case I have to treat them as strings.

>
> I think you have to assume they could be strings.


OK, the following applied patch enables multi-byte locale strings for
numericsep. I also reorganized the code a little.

--
Bruce Momjian | http://candle.pha.pa.us
pgman@candle.pha.pa.us | (610) 359-1001
+ If your life is a hard drive, | 13 Roberts Road
+ Christ can be your backup. | Newtown Square, Pennsylvania 19073

Index: src/bin/psql/print.c
================================================== =================
RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v
retrieving revision 1.68
diff -c -c -r1.68 print.c
*** src/bin/psql/print.c 14 Jul 2005 15:54:21 -0000 1.68
--- src/bin/psql/print.c 14 Jul 2005 20:54:05 -0000
***************
*** 50,125 ****
}

static int
! num_numericseps(const char *my_str)
{
! int old_len, dec_len, int_len;
! int groupdigits = atoi(grouping);

if (my_str[0] == '-')
my_str++;

! old_len = strlen(my_str);
! dec_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0;

- int_len = old_len - dec_len;
if (int_len % groupdigits != 0)
! return int_len / groupdigits;
else
! return int_len / groupdigits - 1; /* no leading separator */
}

static int
len_with_numericsep(const char *my_str)
{
! return strlen(my_str) + num_numericseps(my_str);
}

static void
format_numericsep(char *my_str)
{
! int i, j, digits_before_sep, old_len, new_len, dec_len, int_len;
! char *new_str;
! char *dec_value;
int groupdigits = atoi(grouping);

if (my_str[0] == '-')
my_str++;
!
! old_len = strlen(my_str);
! dec_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0;
! int_len = old_len - dec_len;
! digits_before_sep = int_len % groupdigits;
!
! new_len = int_len + int_len / groupdigits + dec_len;
! if (digits_before_sep == 0)
! new_len--; /* no leading separator */

! new_str = pg_local_malloc(new_len + 1);

for (i=0, j=0; ; i++, j++)
{
! /* hit decimal point */
if (my_str[i] == '.')
{
! new_str[j] = *decimal_point;
! new_str[j+1] = '\0';
! dec_value = strchr(my_str, '.');
! strcat(new_str, ++dec_value);
break;
}

! /* end of string */
if (my_str[i] == '\0')
{
new_str[j] = '\0';
break;
}

! /* add separator? */
! if (i != 0 &&
! (i - (digits_before_sep ? digits_before_sep : groupdigits))
! % groupdigits == 0)
! new_str[j++] = *thousands_sep;

new_str[j] = my_str[i];
}
--- 50,128 ----
}

static int
! integer_digits(const char *my_str)
{
! int frac_len;

if (my_str[0] == '-')
my_str++;

! frac_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0;
!
! return strlen(my_str) - frac_len;
! }
!
! static int
! len_numericseps(const char *my_str)
! {
! int int_len = integer_digits(my_str), sep_len;
! int groupdigits = atoi(grouping);

if (int_len % groupdigits != 0)
! sep_len = int_len / groupdigits;
else
! sep_len = int_len / groupdigits - 1; /* no leading separator */
!
! return sep_len * strlen(thousands_sep) -
! strlen(".") + strlen(decimal_point);
}

static int
len_with_numericsep(const char *my_str)
{
! return strlen(my_str) + len_numericseps(my_str);
}

static void
format_numericsep(char *my_str)
{
! int i, j, int_len = integer_digits(my_str), leading_digits;
int groupdigits = atoi(grouping);
+ char *new_str;

if (my_str[0] == '-')
my_str++;
!
! new_str = pg_local_malloc(len_numericseps(my_str) + 1);

! leading_digits = (int_len % groupdigits != 0) ?
! int_len % groupdigits : groupdigits;

for (i=0, j=0; ; i++, j++)
{
! /* Hit decimal point? */
if (my_str[i] == '.')
{
! strcpy(&new_str[j], decimal_point);
! j += strlen(decimal_point);
! /* add fractional part */
! strcpy(&new_str[j], &my_str[i] + 1);
break;
}

! /* End of string? */
if (my_str[i] == '\0')
{
new_str[j] = '\0';
break;
}

! /* Add separator? */
! if (i != 0 && (i - leading_digits) % groupdigits == 0)
! {
! strcpy(&new_str[j], thousands_sep);
! j += strlen(thousands_sep);
! }

new_str[j] = my_str[i];
}
***************
*** 396,402 ****
int numericseps;

if (opt_align[i % col_count] == 'r' && opt_numericsep)
! numericseps = num_numericseps(*ptr);
else
numericseps = 0;

--- 399,405 ----
int numericseps;

if (opt_align[i % col_count] == 'r' && opt_numericsep)
! numericseps = len_numericseps(*ptr);
else
numericseps = 0;

***************
*** 613,619 ****
int numericseps;

if (opt_align[i % col_count] == 'r' && opt_numericsep)
! numericseps = num_numericseps(*ptr);
else
numericseps = 0;

--- 616,622 ----
int numericseps;

if (opt_align[i % col_count] == 'r' && opt_numericsep)
! numericseps = len_numericseps(*ptr);
else
numericseps = 0;

***************
*** 1711,1717 ****

extlconv = localeconv();

- /* These are treated as single-byte strings in the code */
if (*extlconv->decimal_point)
decimal_point = strdup(extlconv->decimal_point);
else
--- 1714,1719 ----


---------------------------(end of broadcast)---------------------------
TIP 3: Have you checked our extensive FAQ?

http://www.postgresql.org/docs/faq

Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #5 (permalink)  
Old 04-18-2008, 12:40 AM
Bruce Momjian
 
Posts: n/a
Default Re: thousands comma numeric formatting in psql

Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > OK, I have applied the following patch to make numerisep a boolean,

>
> "numericsep" is no longer even remotely reasonable as a name for the
> parameter. Something like "numeric_use_locale" would be appropriate
> (but probably too wordy).


Basically, with the C locale, it changes 1000.00 to 1,000.00, so indeed
is does add a numeric separator. For a European locale, 1000.00 becomes
1.000,00. so in that case is changes both the separator and decimal
marker. We don't currently have a way to display 1000.00 as 1000,00.

I am thinking of calling it just numericlocale.

--
Bruce Momjian | http://candle.pha.pa.us
pgman@candle.pha.pa.us | (610) 359-1001
+ If your life is a hard drive, | 13 Roberts Road
+ Christ can be your backup. | Newtown Square, Pennsylvania 19073

---------------------------(end of broadcast)---------------------------
TIP 6: explain analyze is your friend

Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #6 (permalink)  
Old 04-18-2008, 12:40 AM
Eugen Nedelcu
 
Posts: n/a
Default Re: thousands comma numeric formatting in psql

The new code is broken. Please test it with resonably large tables.
Do not test it with querys like: select -132323435.34343;

If I use a query like:

select * from my_table limit 100;

I can't see anything on my screen until I hit CTRL^C

If I use a pager (\pset pager less) strange things happened:
I can't see anything on my screen, hit CTRL^C, then quit psql,
reset xterm, then less is still running and eat lots of memory and
cpu.

Best Regards,
Eugen

---------------------------(end of broadcast)---------------------------
TIP 2: Don't 'kill -9' the postmaster

Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #7 (permalink)  
Old 04-18-2008, 12:41 AM
Bruce Momjian
 
Posts: n/a
Default Re: thousands comma numeric formatting in psql

Eugen Nedelcu wrote:
> The new code is broken. Please test it with resonably large tables.
> Do not test it with querys like: select -132323435.34343;
>
> If I use a query like:
>
> select * from my_table limit 100;
>
> I can't see anything on my screen until I hit CTRL^C
>
> If I use a pager (\pset pager less) strange things happened:
> I can't see anything on my screen, hit CTRL^C, then quit psql,
> reset xterm, then less is still running and eat lots of memory and
> cpu.


I did:

SELECT random() * 1000000000 INTO test
FROM generate_series(1,100000);
\pset numericsep
SELECT * FROM test;

and it seemed to work fine. Did you do a complete rebuild/install?

--
Bruce Momjian | http://candle.pha.pa.us
pgman@candle.pha.pa.us | (610) 359-1001
+ If your life is a hard drive, | 13 Roberts Road
+ Christ can be your backup. | Newtown Square, Pennsylvania 19073

---------------------------(end of broadcast)---------------------------
TIP 4: Have you searched our list archives?

http://archives.postgresql.org

Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #8 (permalink)  
Old 04-18-2008, 12:41 AM
Eugen Nedelcu
 
Posts: n/a
Default Re: thousands comma numeric formatting in psql

In function format_numericsep() you have:

new_str = pg_local_malloc(len_numericseps(my_str) + 1),

instead of:

new_str = pg_local_malloc(len_with_numericsep(my_str) + 1)

Another bug is in function len_numericseps(). This apear for querys
like:

select NULL::numeric; or
select * from table_with_numeric_fields; where one of numeric fields
have null values;

For such values, len_numericseps returns -1, instead of 0.

Instead of:

if (int_len % groupdigits != 0)
sep_len = int_len / groupdigits;
else
sep_len = int_len / groupdigits - 1;

you must have:

if (int_len % groupdigits != 0 || int_len == 0)
sep_len = int_len / groupdigits;
else
sep_len = int_len / groupdigits - 1;

Best Regards,
Eugen

---------------------------(end of broadcast)---------------------------
TIP 2: Don't 'kill -9' the postmaster

Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #9 (permalink)  
Old 04-18-2008, 12:41 AM
Bruce Momjian
 
Posts: n/a
Default Re: thousands comma numeric formatting in psql


Thanks, fix attached and applied

---------------------------------------------------------------------------

Eugen Nedelcu wrote:
> In function format_numericsep() you have:
>
> new_str = pg_local_malloc(len_numericseps(my_str) + 1),
>
> instead of:
>
> new_str = pg_local_malloc(len_with_numericsep(my_str) + 1)
>
> Another bug is in function len_numericseps(). This apear for querys
> like:
>
> select NULL::numeric; or
> select * from table_with_numeric_fields; where one of numeric fields
> have null values;
>
> For such values, len_numericseps returns -1, instead of 0.
>
> Instead of:
>
> if (int_len % groupdigits != 0)
> sep_len = int_len / groupdigits;
> else
> sep_len = int_len / groupdigits - 1;
>
> you must have:
>
> if (int_len % groupdigits != 0 || int_len == 0)
> sep_len = int_len / groupdigits;
> else
> sep_len = int_len / groupdigits - 1;
>
> Best Regards,
> Eugen
>


--
Bruce Momjian | http://candle.pha.pa.us
pgman@candle.pha.pa.us | (610) 359-1001
+ If your life is a hard drive, | 13 Roberts Road
+ Christ can be your backup. | Newtown Square, Pennsylvania 19073

Index: src/bin/psql/print.c
================================================== =================
RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v
retrieving revision 1.69
diff -c -c -r1.69 print.c
*** src/bin/psql/print.c 14 Jul 2005 21:12:41 -0000 1.69
--- src/bin/psql/print.c 18 Jul 2005 17:16:37 -0000
***************
*** 68,77 ****
int int_len = integer_digits(my_str), sep_len;
int groupdigits = atoi(grouping);

! if (int_len % groupdigits != 0)
! sep_len = int_len / groupdigits;
else
! sep_len = int_len / groupdigits - 1; /* no leading separator */

return sep_len * strlen(thousands_sep) -
strlen(".") + strlen(decimal_point);
--- 68,78 ----
int int_len = integer_digits(my_str), sep_len;
int groupdigits = atoi(grouping);

! if (int_len == 0)
! sep_len = 0;
else
! /* Don't count a leading separator */
! sep_len = int_len / groupdigits - (int_len % groupdigits == 0);

return sep_len * strlen(thousands_sep) -
strlen(".") + strlen(decimal_point);
***************
*** 93,99 ****
if (my_str[0] == '-')
my_str++;

! new_str = pg_local_malloc(len_numericseps(my_str) + 1);

leading_digits = (int_len % groupdigits != 0) ?
int_len % groupdigits : groupdigits;
--- 94,100 ----
if (my_str[0] == '-')
my_str++;

! new_str = pg_local_malloc(len_with_numericsep(my_str) + 1);

leading_digits = (int_len % groupdigits != 0) ?
int_len % groupdigits : groupdigits;


---------------------------(end of broadcast)---------------------------
TIP 4: Have you searched our list archives?

http://archives.postgresql.org

Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #10 (permalink)  
Old 04-18-2008, 12:41 AM
Bruce Momjian
 
Posts: n/a
Default Re: thousands comma numeric formatting in psql

Bruce Momjian wrote:
> Tom Lane wrote:
> > Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > > OK, I have applied the following patch to make numerisep a boolean,

> >
> > "numericsep" is no longer even remotely reasonable as a name for the
> > parameter. Something like "numeric_use_locale" would be appropriate
> > (but probably too wordy).

>
> Basically, with the C locale, it changes 1000.00 to 1,000.00, so indeed
> is does add a numeric separator. For a European locale, 1000.00 becomes
> 1.000,00. so in that case is changes both the separator and decimal
> marker. We don't currently have a way to display 1000.00 as 1000,00.
>
> I am thinking of calling it just numericlocale.


Done, and attached.

--
Bruce Momjian | http://candle.pha.pa.us
pgman@candle.pha.pa.us | (610) 359-1001
+ If your life is a hard drive, | 13 Roberts Road
+ Christ can be your backup. | Newtown Square, Pennsylvania 19073

Index: doc/src/sgml/ref/psql-ref.sgml
================================================== =================
RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v
retrieving revision 1.147
diff -c -c -r1.147 psql-ref.sgml
*** doc/src/sgml/ref/psql-ref.sgml 14 Jul 2005 08:42:36 -0000 1.147
--- doc/src/sgml/ref/psql-ref.sgml 18 Jul 2005 19:38:56 -0000
***************
*** 1493,1499 ****
</varlistentry>

<varlistentry>
! <term><literal>numericsep</literal></term>
<listitem>
<para>
Toggles the display of a locale-aware character to separate groups
--- 1493,1499 ----
</varlistentry>

<varlistentry>
! <term><literal>numericlocale</literal></term>
<listitem>
<para>
Toggles the display of a locale-aware character to separate groups
Index: src/bin/psql/command.c
================================================== =================
RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v
retrieving revision 1.149
diff -c -c -r1.149 command.c
*** src/bin/psql/command.c 14 Jul 2005 08:42:37 -0000 1.149
--- src/bin/psql/command.c 18 Jul 2005 19:39:02 -0000
***************
*** 1420,1435 ****
: _("Expanded display is off.\n"));
}

! /* numeric separators */
! else if (strcmp(param, "numericsep") == 0)
{
! popt->topt.numericSep = !popt->topt.numericSep;
if (!quiet)
{
! if (popt->topt.numericSep)
! puts(_("Showing numeric separators."));
else
! puts(_("Numeric separators are off."));
}
}

--- 1420,1435 ----
: _("Expanded display is off.\n"));
}

! /* locale-aware numeric output */
! else if (strcmp(param, "numericlocale") == 0)
{
! popt->topt.numericLocale = !popt->topt.numericLocale;
if (!quiet)
{
! if (popt->topt.numericLocale)
! puts(_("Showing locale-adjusted numeric output."));
else
! puts(_("Locale-adjusted numeric output is off."));
}
}

Index: src/bin/psql/help.c
================================================== =================
RCS file: /cvsroot/pgsql/src/bin/psql/help.c,v
retrieving revision 1.104
diff -c -c -r1.104 help.c
*** src/bin/psql/help.c 10 Jul 2005 03:46:13 -0000 1.104
--- src/bin/psql/help.c 18 Jul 2005 19:39:02 -0000
***************
*** 239,245 ****
fprintf(output, _(" \\pset NAME [VALUE]\n"
" set table output option\n"
" (NAME := {format|border|expanded|fieldsep|footer|null|\n"
! " numericsep|recordsep|tuples_only|title|tableattr|p ager})\n"));
fprintf(output, _(" \\t show only rows (currently %s)\n"),
ON(pset.popt.topt.tuples_only));
fprintf(output, _(" \\T [STRING] set HTML <table> tag attributes, or unset if none\n"));
--- 239,245 ----
fprintf(output, _(" \\pset NAME [VALUE]\n"
" set table output option\n"
" (NAME := {format|border|expanded|fieldsep|footer|null|\n"
! " numericlocale|recordsep|tuples_only|title|tableatt r|pager})\n"));
fprintf(output, _(" \\t show only rows (currently %s)\n"),
ON(pset.popt.topt.tuples_only));
fprintf(output, _(" \\T [STRING] set HTML <table> tag attributes, or unset if none\n"));
Index: src/bin/psql/print.c
================================================== =================
RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v
retrieving revision 1.71
diff -c -c -r1.71 print.c
*** src/bin/psql/print.c 18 Jul 2005 19:27:37 -0000 1.71
--- src/bin/psql/print.c 18 Jul 2005 19:39:03 -0000
***************
*** 62,69 ****
return strlen(my_str) - frac_len;
}

static int
! len_numericseps(const char *my_str)
{
int int_len = integer_digits(my_str), len = 0;
int groupdigits = atoi(grouping);
--- 62,70 ----
return strlen(my_str) - frac_len;
}

+ /* Return additional length required for locale-aware numeric output */
static int
! additional_numeric_locale_len(const char *my_str)
{
int int_len = integer_digits(my_str), len = 0;
int groupdigits = atoi(grouping);
***************
*** 80,92 ****
}

static int
! len_with_numericsep(const char *my_str)
{
! return strlen(my_str) + len_numericseps(my_str);
}

static void
! format_numericsep(char *my_str)
{
int i, j, int_len = integer_digits(my_str), leading_digits;
int groupdigits = atoi(grouping);
--- 81,93 ----
}

static int
! strlen_with_numeric_locale(const char *my_str)
{
! return strlen(my_str) + additional_numeric_locale_len(my_str);
}

static void
! format_numeric_locale(char *my_str)
{
int i, j, int_len = integer_digits(my_str), leading_digits;
int groupdigits = atoi(grouping);
***************
*** 95,101 ****
if (my_str[0] == '-')
my_str++;

! new_str = pg_local_malloc(len_with_numericsep(my_str) + 1);

leading_digits = (int_len % groupdigits != 0) ?
int_len % groupdigits : groupdigits;
--- 96,102 ----
if (my_str[0] == '-')
my_str++;

! new_str = pg_local_malloc(strlen_with_numeric_locale(my_str) + 1);

leading_digits = (int_len % groupdigits != 0) ?
int_len % groupdigits : groupdigits;
***************
*** 143,149 ****
const char *const *cells, const char *const *footers,
const char *opt_align, const char *opt_fieldsep,
const char *opt_recordsep, bool opt_tuples_only,
! bool opt_numericsep, FILE *fout)
{
unsigned int col_count = 0;
unsigned int i;
--- 144,150 ----
const char *const *cells, const char *const *footers,
const char *opt_align, const char *opt_fieldsep,
const char *opt_recordsep, bool opt_tuples_only,
! bool opt_numeric_locale, FILE *fout)
{
unsigned int col_count = 0;
unsigned int i;
***************
*** 182,193 ****
fputs(opt_recordsep, fout);
need_recordsep = false;
}
! if (opt_align[i % col_count] == 'r' && opt_numericsep)
{
! char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
fputs(my_cell, fout);
free(my_cell);
}
--- 183,194 ----
fputs(opt_recordsep, fout);
need_recordsep = false;
}
! if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
{
! char *my_cell = pg_local_malloc(strlen_with_numeric_locale(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numeric_locale(my_cell);
fputs(my_cell, fout);
free(my_cell);
}
***************
*** 227,233 ****
const char *const *cells,
const char *const *footers, const char *opt_align,
const char *opt_fieldsep, const char *opt_recordsep,
! bool opt_tuples_only, bool opt_numericsep, FILE *fout)
{
unsigned int col_count = 0;
unsigned int i;
--- 228,234 ----
const char *const *cells,
const char *const *footers, const char *opt_align,
const char *opt_fieldsep, const char *opt_recordsep,
! bool opt_tuples_only, bool opt_numeric_locale, FILE *fout)
{
unsigned int col_count = 0;
unsigned int i;
***************
*** 258,269 ****

fputs(headers[i % col_count], fout);
fputs(opt_fieldsep, fout);
! if (opt_align[i % col_count] == 'r' && opt_numericsep)
{
! char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
fputs(my_cell, fout);
free(my_cell);
}
--- 259,270 ----

fputs(headers[i % col_count], fout);
fputs(opt_fieldsep, fout);
! if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
{
! char *my_cell = pg_local_malloc(strlen_with_numeric_locale(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numeric_locale(my_cell);
fputs(my_cell, fout);
free(my_cell);
}
***************
*** 331,337 ****
static void
print_aligned_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
! const char *opt_align, bool opt_tuples_only, bool opt_numericsep,
unsigned short int opt_border, int encoding,
FILE *fout)
{
--- 332,338 ----
static void
print_aligned_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
! const char *opt_align, bool opt_tuples_only, bool opt_numeric_locale,
unsigned short int opt_border, int encoding,
FILE *fout)
{
***************
*** 398,411 ****

for (i = 0, ptr = cells; *ptr; ptr++, i++)
{
! int numericseps;

! if (opt_align[i % col_count] == 'r' && opt_numericsep)
! numericseps = len_numericseps(*ptr);
else
! numericseps = 0;

! tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr), encoding) + numericseps;
if (tmp > widths[i % col_count])
widths[i % col_count] = tmp;
cell_w[i] = tmp;
--- 399,412 ----

for (i = 0, ptr = cells; *ptr; ptr++, i++)
{
! int numeric_locale_len;

! if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
! numeric_locale_len = additional_numeric_locale_len(*ptr);
else
! numeric_locale_len = 0;

! tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr), encoding) + numeric_locale_len;
if (tmp > widths[i % col_count])
widths[i % col_count] = tmp;
cell_w[i] = tmp;
***************
*** 485,496 ****
/* content */
if (opt_align[i % col_count] == 'r')
{
! if (opt_numericsep)
{
char *my_cell = pg_local_malloc(cell_w[i] + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
fprintf(fout, "%*s%s", widths[i % col_count] - cell_w[i], "", my_cell);
free(my_cell);
}
--- 486,497 ----
/* content */
if (opt_align[i % col_count] == 'r')
{
! if (opt_numeric_locale)
{
char *my_cell = pg_local_malloc(cell_w[i] + 1);

strcpy(my_cell, *ptr);
! format_numeric_locale(my_cell);
fprintf(fout, "%*s%s", widths[i % col_count] - cell_w[i], "", my_cell);
free(my_cell);
}
***************
*** 552,558 ****
print_aligned_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
int encoding, FILE *fout)
{
unsigned int col_count = 0;
--- 553,559 ----
print_aligned_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numeric_locale, unsigned short int opt_border,
int encoding, FILE *fout)
{
unsigned int col_count = 0;
***************
*** 615,628 ****
/* find longest data cell */
for (i = 0, ptr = cells; *ptr; ptr++, i++)
{
! int numericseps;

! if (opt_align[i % col_count] == 'r' && opt_numericsep)
! numericseps = len_numericseps(*ptr);
else
! numericseps = 0;

! tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr), encoding) + numericseps;
if (tmp > dwidth)
dwidth = tmp;
cell_w[i] = tmp;
--- 616,629 ----
/* find longest data cell */
for (i = 0, ptr = cells; *ptr; ptr++, i++)
{
! int numeric_locale_len;

! if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
! numeric_locale_len = additional_numeric_locale_len(*ptr);
else
! numeric_locale_len = 0;

! tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr), encoding) + numeric_locale_len;
if (tmp > dwidth)
dwidth = tmp;
cell_w[i] = tmp;
***************
*** 700,707 ****
char *my_cell = pg_local_malloc(cell_w[i] + 1);

strcpy(my_cell, *ptr);
! if (opt_align[i % col_count] == 'r' && opt_numericsep)
! format_numericsep(my_cell);
if (opt_border < 2)
puts(my_cell);
else
--- 701,708 ----
char *my_cell = pg_local_malloc(cell_w[i] + 1);

strcpy(my_cell, *ptr);
! if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
! format_numeric_locale(my_cell);
if (opt_border < 2)
puts(my_cell);
else
***************
*** 788,794 ****
print_html_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
const char *opt_table_attr, FILE *fout)
{
unsigned int col_count = 0;
--- 789,795 ----
print_html_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numeric_locale, unsigned short int opt_border,
const char *opt_table_attr, FILE *fout)
{
unsigned int col_count = 0;
***************
*** 834,845 ****
/* is string only whitespace? */
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs("&nbsp; ", fout);
! else if (opt_align[i % col_count] == 'r' && opt_numericsep)
{
! char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
html_escaped_print(my_cell, fout);
free(my_cell);
}
--- 835,846 ----
/* is string only whitespace? */
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs("&nbsp; ", fout);
! else if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
{
! char *my_cell = pg_local_malloc(strlen_with_numeric_locale(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numeric_locale(my_cell);
html_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 875,881 ****
print_html_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
const char *opt_table_attr, FILE *fout)
{
unsigned int col_count = 0;
--- 876,882 ----
print_html_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numeric_locale, unsigned short int opt_border,
const char *opt_table_attr, FILE *fout)
{
unsigned int col_count = 0;
***************
*** 919,930 ****
/* is string only whitespace? */
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs("&nbsp; ", fout);
! else if (opt_align[i % col_count] == 'r' && opt_numericsep)
{
! char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
html_escaped_print(my_cell, fout);
free(my_cell);
}
--- 920,931 ----
/* is string only whitespace? */
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
fputs("&nbsp; ", fout);
! else if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
{
! char *my_cell = pg_local_malloc(strlen_with_numeric_locale(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numeric_locale(my_cell);
html_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 1000,1006 ****
print_latex_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
--- 1001,1007 ----
print_latex_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numeric_locale, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
***************
*** 1061,1072 ****
/* print cells */
for (i = 0, ptr = cells; *ptr; i++, ptr++)
{
! if (opt_numericsep)
{
! char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
latex_escaped_print(my_cell, fout);
free(my_cell);
}
--- 1062,1073 ----
/* print cells */
for (i = 0, ptr = cells; *ptr; i++, ptr++)
{
! if (opt_numeric_locale)
{
! char *my_cell = pg_local_malloc(strlen_with_numeric_locale(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numeric_locale(my_cell);
latex_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 1103,1109 ****
print_latex_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
--- 1104,1110 ----
print_latex_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numeric_locale, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
***************
*** 1174,1185 ****
if (footers && !opt_tuples_only)
for (ptr = footers; *ptr; ptr++)
{
! if (opt_numericsep)
{
! char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
latex_escaped_print(my_cell, fout);
free(my_cell);
}
--- 1175,1186 ----
if (footers && !opt_tuples_only)
for (ptr = footers; *ptr; ptr++)
{
! if (opt_numeric_locale)
{
! char *my_cell = pg_local_malloc(strlen_with_numeric_locale(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numeric_locale(my_cell);
latex_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 1220,1226 ****
print_troff_ms_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
--- 1221,1227 ----
print_troff_ms_text(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numeric_locale, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
***************
*** 1274,1285 ****
/* print cells */
for (i = 0, ptr = cells; *ptr; i++, ptr++)
{
! if (opt_numericsep)
{
! char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
troff_ms_escaped_print(my_cell, fout);
free(my_cell);
}
--- 1275,1286 ----
/* print cells */
for (i = 0, ptr = cells; *ptr; i++, ptr++)
{
! if (opt_numeric_locale)
{
! char *my_cell = pg_local_malloc(strlen_with_numeric_locale(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numeric_locale(my_cell);
troff_ms_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 1313,1319 ****
print_troff_ms_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numericsep, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
--- 1314,1320 ----
print_troff_ms_vertical(const char *title, const char *const *headers,
const char *const *cells, const char *const *footers,
const char *opt_align, bool opt_tuples_only,
! bool opt_numeric_locale, unsigned short int opt_border,
FILE *fout)
{
unsigned int col_count = 0;
***************
*** 1386,1397 ****

troff_ms_escaped_print(headers[i % col_count], fout);
fputc('\t', fout);
! if (opt_numericsep)
{
! char *my_cell = pg_local_malloc(len_with_numericsep(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numericsep(my_cell);
troff_ms_escaped_print(my_cell, fout);
free(my_cell);
}
--- 1387,1398 ----

troff_ms_escaped_print(headers[i % col_count], fout);
fputc('\t', fout);
! if (opt_numeric_locale)
{
! char *my_cell = pg_local_malloc(strlen_with_numeric_locale(*ptr) + 1);

strcpy(my_cell, *ptr);
! format_numeric_locale(my_cell);
troff_ms_escaped_print(my_cell, fout);
free(my_cell);
}
***************
*** 1533,1539 ****
/* print the stuff */

if (flog)
! print_aligned_text(title, headers, cells, footers, align, opt->tuples_only, opt->numericSep, border, opt->encoding, flog);

switch (opt->format)
{
--- 1534,1540 ----
/* print the stuff */

if (flog)
! print_aligned_text(title, headers, cells, footers, align, opt->tuples_only, opt->numericLocale, border, opt->encoding, flog);

switch (opt->format)
{
***************
*** 1541,1590 ****
if (use_expanded)
print_unaligned_vertical(title, headers, cells, footers, align,
opt->fieldSep, opt->recordSep,
! opt->tuples_only, opt->numericSep, output);
else
print_unaligned_text(title, headers, cells, footers, align,
opt->fieldSep, opt->recordSep,
! opt->tuples_only, opt->numericSep, output);
break;
case PRINT_ALIGNED:
if (use_expanded)
print_aligned_vertical(title, headers, cells, footers, align,
! opt->tuples_only, opt->numericSep, border,
opt->encoding, output);
else
print_aligned_text(title, headers, cells, footers, align,
! opt->tuples_only, opt->numericSep,
border, opt->encoding, output);
break;
case PRINT_HTML:
if (use_expanded)
print_html_vertical(title, headers, cells, footers, align,
! opt->tuples_only, opt->numericSep,
border, opt->tableAttr, output);
else
print_html_text(title, headers, cells, footers,
! align, opt->tuples_only, opt->numericSep, border,
opt->tableAttr, output);
break;
case PRINT_LATEX:
if (use_expanded)
print_latex_vertical(title, headers, cells, footers, align,
! opt->tuples_only, opt->numericSep,
border, output);
else
print_latex_text(title, headers, cells, footers, align,
! opt->tuples_only, opt->numericSep,
border, output);
break;
case PRINT_TROFF_MS:
if (use_expanded)
print_troff_ms_vertical(title, headers, cells, footers, align,
! opt->tuples_only, opt->numericSep,
border, output);
else
print_troff_ms_text(title, headers, cells, footers, align,
! opt->tuples_only, opt->numericSep,
border, output);
break;
default:
--- 1542,1591 ----
if (use_expanded)
print_unaligned_vertical(title, headers, cells, footers, align,
opt->fieldSep, opt->recordSep,
! opt->tuples_only, opt->numericLocale, output);
else
print_unaligned_text(title, headers, cells, footers, align,
opt->fieldSep, opt->recordSep,
! opt->tuples_only, opt->numericLocale, output);
break;
case PRINT_ALIGNED:
if (use_expanded)
print_aligned_vertical(title, headers, cells, footers, align,