Thread: thousands comma numeric formatting in psql

thousands comma numeric formatting in psql

From
Eugen Nedelcu
Date:
Hello,

This is my first post to this list.
Sorry if my english it's not so good. It's not my native language.

I'm interrested to now if someone think that having a feature like
'thousands comma delimited numeric formatting' in psql it's a
usefull thing.

I've made a patch for psql that adds this feature, so issuing a
select like this:

my_database=> select 1234567.89;

results in:

   ?column?
--------------
 1,234,567.89

This feature is toggle on/off with a backslash command ('\n'):

my_database=> \n
Numeric formatting is off.

my_database=> select 1234567.89;

   ?column?
--------------
 1234567.89

For me, psql it's still the best client for postgres, faster and
flexible than graphic ones. And having tables with many numeric
columns witch contain huge numbers make difficult to read this
numbers.

One solution to deal with this is to use to_char function, but for
complex selects against multiple tables it's not a good option.

Another one is to make a custom function that works like this:

select my_function(... complex subselect ...);

but this seems ugly to me.

By adding the '\n' switch to psql I can make any complex select and
have all numeric fields in result formatted in easy readable form.

I'm not an expert in postgresql, so if someone thinks there is an
easier way to deal with numeric fields, please share.

If my idea is considered usefull I can post the patch to this list.

Best regards,
Eugen.

Re: thousands comma numeric formatting in psql

From
Bruno Wolff III
Date:
On Tue, Jun 21, 2005 at 08:42:16 +0300,
  Eugen Nedelcu <eugen@sifolt.ro> wrote:
>
> One solution to deal with this is to use to_char function, but for
> complex selects against multiple tables it's not a good option.

Why not? You only have to apply it to the output expressions that
need it.

Note that if you output numbers like this, you also need to be able to
read them back in. I don't think adding complexity for doing that is
worth not having to add a few to_char calls in your select queries.

Re: thousands comma numeric formatting in psql

From
Eugen Nedelcu
Date:
On Tue, Jun 21, 2005 at 06:59:38AM -0500, Bruno Wolff III wrote:
> On Tue, Jun 21, 2005 at 08:42:16 +0300,
>   Eugen Nedelcu <eugen@sifolt.ro> wrote:
> >
> > One solution to deal with this is to use to_char function, but for
> > complex selects against multiple tables it's not a good option.
>
> Why not? You only have to apply it to the output expressions that
> need it.

I think this:

select * from table_with_text_and_numeric_fields;

is much,much easier than:

select text_field1,text_field2,to_char(numeric_field1, '99G999G999'),
       to_char(numeric_field2, '9G999G999G999'), text_field3,
       text_field4, to_char(numeric_field3, 'MI90G999D99') from
       table_with_text_and_numeric_fields;

>
> Note that if you output numbers like this, you also need to be able to
> read them back in. I don't think adding complexity for doing that is
> worth not having to add a few to_char calls in your select queries.
>

I don't know what 'read them back in' means to you.
This formatting is only done when the number is output to the screen.

Something like:
    fputs(thousands_comma_number, fout)
instead of:
    fputs(original_number, fout)

If you want to output to some file for reading back later, you could
turn the feature off with the backslash switch '\n'.

This is a patch for psql client and not for the backend. It's role
is to output numbers to screen in easy readable form (2,345,675,454,543
is much easier to read then 2345675454543.456). I think graphical
clients like pgAdmin or phppgadmin have a way to do this. I don't know
this for sure but I will investigate it.

Best Regards,
Eugen

Re: thousands comma numeric formatting in psql

From
Alvaro Herrera
Date:
On Tue, Jun 21, 2005 at 04:03:43PM +0300, Eugen Nedelcu wrote:

> This is a patch for psql client and not for the backend. It's role
> is to output numbers to screen in easy readable form (2,345,675,454,543
> is much easier to read then 2345675454543.456). I think graphical
> clients like pgAdmin or phppgadmin have a way to do this. I don't know
> this for sure but I will investigate it.

I think it would be much nicer if it was a backend setting.  If it
depended on the locale setting (LC_NUMERIC, I think) and worked for both
input and output, it would be very good.

--
Alvaro Herrera (<alvherre[a]surnet.cl>)
"Uno puede defenderse de los ataques; contra los elogios se esta indefenso"

Re: thousands comma numeric formatting in psql

From
Bruno Wolff III
Date:
On Tue, Jun 21, 2005 at 16:03:43 +0300,
  Eugen Nedelcu <eugen@sifolt.ro> wrote:
>
> This is a patch for psql client and not for the backend. It's role
> is to output numbers to screen in easy readable form (2,345,675,454,543
> is much easier to read then 2345675454543.456). I think graphical
> clients like pgAdmin or phppgadmin have a way to do this. I don't know
> this for sure but I will investigate it.

I did miss that this was a psql only feature. I still don't see a great
need, but it isn't going to cause a problem for copy or pg_dump that way.

Re: thousands comma numeric formatting in psql

From
Tom Lane
Date:
Alvaro Herrera <alvherre@surnet.cl> writes:
> On Tue, Jun 21, 2005 at 04:03:43PM +0300, Eugen Nedelcu wrote:
>> This is a patch for psql client and not for the backend.

> I think it would be much nicer if it was a backend setting.

Doing this as a backend setting has been proposed and rejected before.
The risk of breaking client code seems to outweigh any possible value.

As a psql setting, though, it seems relatively harmless --- certainly
no more dangerous than \x or the other pset formatting parameters.

> If it depended on the locale setting (LC_NUMERIC, I think)

Yes, I wanted to ask that too --- if the patch can cope with locales that
exchange the roles of comma and period, that would make a lot of people
very happy.

            regards, tom lane

Re: thousands comma numeric formatting in psql

From
Eugen Nedelcu
Date:
Hello,

I have included my patch attached to this mail.

I have made the changes to deal with locale settings from client
environment. So now you can start psql like this:

(export LC_ALL=ro_RO; psql -U user db)

and have numeric formatting with '.' as thousands separator and
',' as decimal point, or

(export LC_ALL=en_US; psql -U user db)

and have numeric formatting with ',' as thousands separator and
'.' as decimal point. This formatting is default when locale is 'C'

You can set any locale and numeric formatting code will take it in
consideration.

This patch is for version 7.3.2. The steps for install is:

1) cp thousands_comma.diff $POSTGRES_DIR/src/bin/psql
2) cd $POSTGRES_DIR/src/bin/psql
3) patch -p0 < thousands_comma.diff
4) ../../../configure && make


Best Regards,
Eugen

Attachment

Re: thousands comma numeric formatting in psql

From
Bruce Momjian
Date:
Eugen Nedelcu wrote:
> Hello,
>
> I have included my patch attached to this mail.
>
> I have made the changes to deal with locale settings from client
> environment. So now you can start psql like this:
>
> (export LC_ALL=ro_RO; psql -U user db)
>
> and have numeric formatting with '.' as thousands separator and
> ',' as decimal point, or
>
> (export LC_ALL=en_US; psql -U user db)
>
> and have numeric formatting with ',' as thousands separator and
> '.' as decimal point. This formatting is default when locale is 'C'
>
> You can set any locale and numeric formatting code will take it in
> consideration.
>
> This patch is for version 7.3.2. The steps for install is:
>
> 1) cp thousands_comma.diff $POSTGRES_DIR/src/bin/psql
> 2) cd $POSTGRES_DIR/src/bin/psql
> 3) patch -p0 < thousands_comma.diff
> 4) ../../../configure && make

I have heavily modified your patch and have applied it.  Instead of
using langinfo, I used a \pset variable numericsep. (We can talk about
adding langinfo detection later.)  By default, it is off, ''.  If you
set it to '.', the decimal marker will be ','.  This also allows
separators like ' ' too so numebers can appear as 100 000.

I have also added documentation.

--
  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.145
diff -c -c -r1.145 psql-ref.sgml
*** doc/src/sgml/ref/psql-ref.sgml    14 Jun 2005 02:57:38 -0000    1.145
--- doc/src/sgml/ref/psql-ref.sgml    10 Jul 2005 03:24:16 -0000
***************
*** 1493,1498 ****
--- 1493,1510 ----
            </varlistentry>

            <varlistentry>
+           <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>
+
+           <varlistentry>
            <term><literal>recordsep</literal></term>
            <listitem>
            <para>
Index: src/bin/psql/command.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v
retrieving revision 1.146
diff -c -c -r1.146 command.c
*** src/bin/psql/command.c    13 Jun 2005 06:36:22 -0000    1.146
--- src/bin/psql/command.c    10 Jul 2005 03:24:17 -0000
***************
*** 838,844 ****
      else if (strcmp(cmd, "x") == 0)
          success = do_pset("expanded", NULL, &pset.popt, quiet);

-
      /* \z -- list table rights (equivalent to \dp) */
      else if (strcmp(cmd, "z") == 0)
      {
--- 838,843 ----
***************
*** 1421,1426 ****
--- 1420,1436 ----
                     : _("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 */
      else if (strcmp(param, "null") == 0)
      {
Index: src/bin/psql/help.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/help.c,v
retrieving revision 1.103
diff -c -c -r1.103 help.c
*** src/bin/psql/help.c    6 Jul 2005 03:14:48 -0000    1.103
--- src/bin/psql/help.c    10 Jul 2005 03:24:18 -0000
***************
*** 239,245 ****
      fprintf(output, _("  \\pset NAME [VALUE]\n"
                        "                 set table output option\n"
                        "                 (NAME := {format|border|expanded|fieldsep|footer|null|\n"
!     "                 recordsep|tuples_only|title|tableattr|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"));
--- 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|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.60
diff -c -c -r1.60 print.c
*** src/bin/psql/print.c    14 Jun 2005 22:15:57 -0000    1.60
--- src/bin/psql/print.c    10 Jul 2005 03:24:24 -0000
***************
*** 29,48 ****

  #include "mbprint.h"

  /*************************/
  /* Unaligned text         */
  /*************************/


  static void
! print_unaligned_text(const char *title, const char *const * headers,
!                   const char *const * cells, const char *const * footers,
!  const char *opt_fieldsep, const char *opt_recordsep, bool opt_barebones,
!                      FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const * ptr;
      bool        need_recordsep = false;

      if (!opt_fieldsep)
--- 29,135 ----

  #include "mbprint.h"

+ static int
+ num_numericseps(const char *my_str)
+ {
+     int old_len, dec_len, int_len;
+
+     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 % 3 != 0)
+         return int_len / 3;
+     else
+         return int_len / 3 - 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, 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++;
+
+     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 */
+
+     new_str = malloc(new_len);
+     if (!new_str)
+     {
+         fprintf(stderr, _("out of memory\n"));
+         exit(EXIT_FAILURE);
+     }
+
+     for (i=0, j=0; ; i++, j++)
+     {
+         /* 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);
+             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 : 3)) % 3 == 0)
+             new_str[j++] = *numericsep;
+
+         new_str[j] = my_str[i];
+     }
+
+     strcpy(my_str, new_str);
+     free(new_str);
+ }
+
  /*************************/
  /* Unaligned text         */
  /*************************/


  static void
! print_unaligned_text(const char *title, const char *const *headers,
!                      const char *const *cells, const char *const *footers,
!                      const char *opt_align, const char *opt_fieldsep,
!                      const char *opt_recordsep, bool opt_barebones,
!                      char *opt_numericsep, FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const *ptr;
      bool        need_recordsep = false;

      if (!opt_fieldsep)
***************
*** 77,83 ****
              fputs(opt_recordsep, fout);
              need_recordsep = false;
          }
!         fputs(*ptr, fout);
          if ((i + 1) % col_count)
              fputs(opt_fieldsep, fout);
          else
--- 164,187 ----
              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 = malloc(len_with_numericsep(*ptr));
!
!             if (!my_cell)
!             {
!                 fprintf(stderr, _("out of memory\n"));
!                 exit(EXIT_FAILURE);
!             }
!             strcpy(my_cell, *ptr);
!             format_numericsep(my_cell, opt_numericsep);
!             fputs(my_cell, fout);
!             free(my_cell);
!         }
!         else
!             fputs(*ptr, fout);
!
          if ((i + 1) % col_count)
              fputs(opt_fieldsep, fout);
          else
***************
*** 107,120 ****


  static void
! print_unaligned_vertical(const char *title, const char *const * headers,
!                   const char *const * cells, const char *const * footers,
!  const char *opt_fieldsep, const char *opt_recordsep, bool opt_barebones,
!                          FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const * ptr;

      if (!opt_fieldsep)
          opt_fieldsep = "";
--- 211,225 ----


  static void
! print_unaligned_vertical(const char *title, const char *const *headers,
!                          const char *const *cells,
!                          const char *const *footers, const char *opt_align,
!                          const char *opt_fieldsep, const char *opt_recordsep,
!                          bool opt_barebones, char *opt_numericsep, FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const *ptr;

      if (!opt_fieldsep)
          opt_fieldsep = "";
***************
*** 141,147 ****

          fputs(headers[i % col_count], fout);
          fputs(opt_fieldsep, fout);
!         fputs(*ptr, fout);
      }

      /* print footers */
--- 246,268 ----

          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 = malloc(len_with_numericsep(*ptr));
!
!             if (!my_cell)
!             {
!                 fprintf(stderr, _("out of memory\n"));
!                 exit(EXIT_FAILURE);
!             }
!             strcpy(my_cell, *ptr);
!             format_numericsep(my_cell, opt_numericsep);
!             fputs(my_cell, fout);
!             free(my_cell);
!         }
!         else
!             fputs(*ptr, fout);
      }

      /* print footers */
***************
*** 202,210 ****


  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_barebones,
                     unsigned short int opt_border, int encoding,
                     FILE *fout)
  {
--- 323,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_barebones, char *opt_numericsep,
                     unsigned short int opt_border, int encoding,
                     FILE *fout)
  {
***************
*** 216,222 ****
                  tmp;
      unsigned int *widths,
                  total_w;
!     const char *const * ptr;

      /* count columns */
      for (ptr = headers; *ptr; ptr++)
--- 337,343 ----
                  tmp;
      unsigned int *widths,
                  total_w;
!     const char *const *ptr;

      /* count columns */
      for (ptr = headers; *ptr; ptr++)
***************
*** 271,277 ****

      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
!         tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr), encoding);
          if (tmp > widths[i % col_count])
              widths[i % col_count] = tmp;
          cell_w[i] = tmp;
--- 392,406 ----

      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
!         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;
!
!         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;
***************
*** 351,358 ****
          /* content */
          if (opt_align[i % col_count] == 'r')
          {
!             fprintf(fout, "%*s%s",
!                     widths[i % col_count] - cell_w[i], "", cells[i]);
          }
          else
          {
--- 480,501 ----
          /* content */
          if (opt_align[i % col_count] == 'r')
          {
!             if (strlen(*ptr) > 0 && opt_numericsep != NULL && strlen(opt_numericsep) > 0)
!             {
!                 char *my_cell = malloc(cell_w[i]);
!
!                 if (!my_cell)
!                 {
!                     fprintf(stderr, _("out of memory\n"));
!                     exit(EXIT_FAILURE);
!                 }
!                 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);
!             }
!             else
!                 fprintf(fout, "%*s%s", widths[i % col_count] - cell_w[i], "", *ptr);
          }
          else
          {
***************
*** 406,419 ****


  static void
! print_aligned_vertical(const char *title, const char *const * headers,
!                   const char *const * cells, const char *const * footers,
!                        bool opt_barebones, unsigned short int opt_border,
                         int encoding, FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int record = 1;
!     const char *const * ptr;
      unsigned int i,
                  tmp = 0,
                  hwidth = 0,
--- 549,563 ----


  static void
! print_aligned_vertical(const char *title, const char *const *headers,
!                        const char *const *cells, const char *const *footers,
!                        const char *opt_align, bool opt_barebones,
!                        char *opt_numericsep, unsigned short int opt_border,
                         int encoding, FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int record = 1;
!     const char *const *ptr;
      unsigned int i,
                  tmp = 0,
                  hwidth = 0,
***************
*** 471,477 ****
      /* find longest data cell */
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
!         tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr), encoding);
          if (tmp > dwidth)
              dwidth = tmp;
          cell_w[i] = tmp;
--- 615,629 ----
      /* find longest data cell */
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
!         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;
!
!         tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr), encoding) + numericseps;
          if (tmp > dwidth)
              dwidth = tmp;
          cell_w[i] = tmp;
***************
*** 556,565 ****
          else
              fputs(" ", fout);

!         if (opt_border < 2)
!             fprintf(fout, "%s\n", *ptr);
!         else
!             fprintf(fout, "%-s%*s |\n", *ptr, dwidth - cell_w[i], "");
      }

      if (opt_border == 2)
--- 708,731 ----
          else
              fputs(" ", fout);

!         {
!             char *my_cell = malloc(cell_w[i]);
!
!             if (!my_cell)
!             {
!                 fprintf(stderr, _("out of memory\n"));
!                 exit(EXIT_FAILURE);
!             }
!             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
!                 fprintf(fout, "%-s%*s |\n", my_cell, dwidth - cell_w[i], "");
!             free(my_cell);
!         }
      }

      if (opt_border == 2)
***************
*** 637,651 ****


  static void
! print_html_text(const char *title, const char *const * headers,
!                 const char *const * cells, const char *const * footers,
! const char *opt_align, bool opt_barebones, unsigned short int opt_border,
!                 const char *opt_table_attr,
!                 FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const * ptr;

      fprintf(fout, "<table border=\"%d\"", opt_border);
      if (opt_table_attr)
--- 803,817 ----


  static void
! print_html_text(const char *title, const char *const *headers,
!                 const char *const *cells, const char *const *footers,
!                 const char *opt_align, bool opt_barebones,
!                 char *opt_numericsep, unsigned short int opt_border,
!                 const char *opt_table_attr, FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const *ptr;

      fprintf(fout, "<table border=\"%d\"", opt_border);
      if (opt_table_attr)
***************
*** 683,693 ****
              fputs("  <tr valign=\"top\">\n", fout);

          fprintf(fout, "    <td align=\"%s\">", opt_align[(i) % col_count] == 'r' ? "right" : "left");
!         if ((*ptr)[strspn(*ptr, " \t")] == '\0')        /* is string only
!                                                          * whitespace? */
              fputs("  ", fout);
          else
              html_escaped_print(*ptr, fout);
          fputs("</td>\n", fout);

          if ((i + 1) % col_count == 0)
--- 849,875 ----
              fputs("  <tr valign=\"top\">\n", fout);

          fprintf(fout, "    <td align=\"%s\">", opt_align[(i) % col_count] == 'r' ? "right" : "left");
!         /* is string only whitespace? */
!         if ((*ptr)[strspn(*ptr, " \t")] == '\0')
              fputs("  ", fout);
+         else if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
+                  opt_numericsep != NULL && strlen(opt_numericsep) > 0)
+         {
+             char *my_cell = malloc(len_with_numericsep(*ptr));
+
+             if (!my_cell)
+             {
+                 fprintf(stderr, _("out of memory\n"));
+                 exit(EXIT_FAILURE);
+             }
+             strcpy(my_cell, *ptr);
+             format_numericsep(my_cell, opt_numericsep);
+             html_escaped_print(my_cell, fout);
+             free(my_cell);
+         }
          else
              html_escaped_print(*ptr, fout);
+
          fputs("</td>\n", fout);

          if ((i + 1) % col_count == 0)
***************
*** 714,729 ****


  static void
! print_html_vertical(const char *title, const char *const * headers,
!                   const char *const * cells, const char *const * footers,
! const char *opt_align, bool opt_barebones, unsigned short int opt_border,
!                     const char *opt_table_attr,
!                     FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
      unsigned int record = 1;
!     const char *const * ptr;

      fprintf(fout, "<table border=\"%d\"", opt_border);
      if (opt_table_attr)
--- 896,911 ----


  static void
! print_html_vertical(const char *title, const char *const *headers,
!                   const char *const *cells, const char *const *footers,
!                   const char *opt_align, bool opt_barebones,
!                   char *opt_numericsep, unsigned short int opt_border,
!                   const char *opt_table_attr, FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
      unsigned int record = 1;
!     const char *const *ptr;

      fprintf(fout, "<table border=\"%d\"", opt_border);
      if (opt_table_attr)
***************
*** 758,768 ****
          fputs("</th>\n", fout);

          fprintf(fout, "    <td align=\"%s\">", opt_align[i % col_count] == 'r' ? "right" : "left");
!         if ((*ptr)[strspn(*ptr, " \t")] == '\0')        /* is string only
!                                                          * whitespace? */
              fputs("  ", fout);
          else
              html_escaped_print(*ptr, fout);
          fputs("</td>\n  </tr>\n", fout);
      }

--- 940,966 ----
          fputs("</th>\n", fout);

          fprintf(fout, "    <td align=\"%s\">", opt_align[i % col_count] == 'r' ? "right" : "left");
!         /* is string only whitespace? */
!         if ((*ptr)[strspn(*ptr, " \t")] == '\0')
              fputs("  ", fout);
+         else if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
+             opt_numericsep != NULL && strlen(opt_numericsep) > 0)
+         {
+             char *my_cell = malloc(len_with_numericsep(*ptr));
+
+             if (!my_cell)
+             {
+                 fprintf(stderr, _("out of memory\n"));
+                 exit(EXIT_FAILURE);
+             }
+             strcpy(my_cell, *ptr);
+             format_numericsep(my_cell, opt_numericsep);
+             html_escaped_print(my_cell, fout);
+             free(my_cell);
+         }
          else
              html_escaped_print(*ptr, fout);
+
          fputs("</td>\n  </tr>\n", fout);
      }

***************
*** 829,842 ****


  static void
! print_latex_text(const char *title, const char *const * headers,
!                  const char *const * cells, const char *const * footers,
! const char *opt_align, bool opt_barebones, unsigned short int opt_border,
!                  FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const * ptr;


      /* print title */
--- 1027,1040 ----


  static void
! print_latex_text(const char *title, const char *const *headers,
!                  const char *const *cells, const char *const *footers,
!                  const char *opt_align, bool opt_barebones,
!                  unsigned short int opt_border, FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const *ptr;


      /* print title */
***************
*** 921,934 ****


  static void
! print_latex_vertical(const char *title, const char *const * headers,
!                   const char *const * cells, const char *const * footers,
! const char *opt_align, bool opt_barebones, unsigned short int opt_border,
!                      FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const * ptr;
      unsigned int record = 1;

      (void) opt_align;            /* currently unused parameter */
--- 1119,1132 ----


  static void
! print_latex_vertical(const char *title, const char *const *headers,
!                   const char *const *cells, const char *const *footers,
!                   const char *opt_align, bool opt_barebones,
!                   unsigned short int opt_border, FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const *ptr;
      unsigned int record = 1;

      (void) opt_align;            /* currently unused parameter */
***************
*** 1027,1040 ****


  static void
! 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_barebones, unsigned short int opt_border,
!                  FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const * ptr;


      /* print title */
--- 1225,1238 ----


  static void
! 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_barebones,
!                  unsigned short int opt_border, FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const *ptr;


      /* print title */
***************
*** 1111,1124 ****


  static void
! 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_barebones, unsigned short int opt_border,
!                      FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const * ptr;
      unsigned int record = 1;
          unsigned short current_format = 0; /* 0=none, 1=header, 2=body */

--- 1309,1322 ----


  static void
! 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_barebones,
!                   unsigned short int opt_border, FILE *fout)
  {
      unsigned int col_count = 0;
      unsigned int i;
!     const char *const *ptr;
      unsigned int record = 1;
          unsigned short current_format = 0; /* 0=none, 1=header, 2=body */

***************
*** 1263,1271 ****

  void
  printTable(const char *title,
!            const char *const * headers,
!            const char *const * cells,
!            const char *const * footers,
             const char *align,
             const printTableOpt *opt, FILE *fout, FILE *flog)
  {
--- 1461,1469 ----

  void
  printTable(const char *title,
!            const char *const *headers,
!            const char *const *cells,
!            const char *const *footers,
             const char *align,
             const printTableOpt *opt, FILE *fout, FILE *flog)
  {
***************
*** 1298,1304 ****
          int            col_count = 0,
                      row_count = 0,
                      lines;
!         const char *const * ptr;

          /* rough estimate of columns and rows */
          if (headers)
--- 1496,1502 ----
          int            col_count = 0,
                      row_count = 0,
                      lines;
!         const char *const *ptr;

          /* rough estimate of columns and rows */
          if (headers)
***************
*** 1325,1362 ****
      /* print the stuff */

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

      switch (opt->format)
      {
          case PRINT_UNALIGNED:
              if (use_expanded)
!                 print_unaligned_vertical(title, headers, cells, footers,
                                           opt->fieldSep, opt->recordSep,
!                                          opt->tuples_only, output);
              else
!                 print_unaligned_text(title, headers, cells, footers,
                                       opt->fieldSep, opt->recordSep,
!                                      opt->tuples_only, output);
              break;
          case PRINT_ALIGNED:
              if (use_expanded)
!                 print_aligned_vertical(title, headers, cells, footers,
!                                        opt->tuples_only, border,
                                         opt->encoding, output);
              else
!                 print_aligned_text(title, headers, cells, footers,
!                                    align, opt->tuples_only,
                                     border, opt->encoding, output);
              break;
          case PRINT_HTML:
              if (use_expanded)
!                 print_html_vertical(title, headers, cells, footers,
!                                     align, opt->tuples_only,
                                      border, opt->tableAttr, output);
              else
                  print_html_text(title, headers, cells, footers,
!                                 align, opt->tuples_only, border,
                                  opt->tableAttr, output);
              break;
          case PRINT_LATEX:
--- 1523,1560 ----
      /* 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)
      {
          case PRINT_UNALIGNED:
              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:
Index: src/bin/psql/print.h
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v
retrieving revision 1.25
diff -c -c -r1.25 print.h
*** src/bin/psql/print.h    14 Jun 2005 02:57:41 -0000    1.25
--- src/bin/psql/print.h    10 Jul 2005 03:24:24 -0000
***************
*** 40,45 ****
--- 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

Re: thousands comma numeric formatting in psql

From
Peter Eisentraut
Date:
Bruce Momjian wrote:
> I have heavily modified your patch and have applied it.  Instead of
> using langinfo, I used a \pset variable numericsep.

Why?

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

Re: thousands comma numeric formatting in psql

From
Bruce Momjian
Date:
Peter Eisentraut wrote:
> Bruce Momjian wrote:
> > I have heavily modified your patch and have applied it.  Instead of
> > using langinfo, I used a \pset variable numericsep.
>
> Why?

Because I don't have langinfo on my system, so I can't test it, nor add
configure code for it.  It also prevents us from using space as the
separator, which is the international standard.

If you think we should use langinfo, we can discuss it, but at this
point, it is not used.

--
  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

Re: thousands comma numeric formatting in psql

From
Bruce Momjian
Date:
Thanks, fixed, and applied.  I also centralized the malloc into a
function.  We could use pg_malloc, but that doesn't export out to
/scripts, where this is shared.  We will fix that some day, but not
right now.

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

Eugen Nedelcu wrote:
> On Mon, Jul 11, 2005 at 11:37:17PM -0400, Bruce Momjian wrote:
> >
> > Did you do a 'make clean' before testing?  I can't reproduce any failure
> > here.  Can you supply a number that is failing, like this:
> >
> >     SELECT 1000000;
> >
> > ---------------------------------------------------------------------------
> >
>
> The bug is in the following code:
>
> new_str = malloc(new_len) and every one of
> char *my_cell = malloc(....)
>
> Ring a bell?
>
> The corect form is:
>
> new_str = malloc(new_len + 1),
> char *my_cell = malloc(.... + 1)
>
> because of the null character that must terminate the string (\0)
>
> The error apears of course randomly (more probably for a query which
> returns many rows)
>
> 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).
>

--
  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.63
diff -c -c -r1.63 print.c
*** src/bin/psql/print.c    10 Jul 2005 15:53:42 -0000    1.63
--- src/bin/psql/print.c    14 Jul 2005 06:44:10 -0000
***************
*** 29,34 ****
--- 29,48 ----

  #include "mbprint.h"

+ static void *
+ pg_local_malloc(size_t size)
+ {
+     void       *tmp;
+
+     tmp = malloc(size);
+     if (!tmp)
+     {
+         psql_error("out of memory\n");
+         exit(EXIT_FAILURE);
+     }
+     return tmp;
+ }
+
  static int
  num_numericseps(const char *my_str)
  {
***************
*** 46,51 ****
--- 60,66 ----
      else
          return int_len / 3 - 1;    /* no leading separator */
  }
+
  static int
  len_with_numericsep(const char *my_str)
  {
***************
*** 77,88 ****
      if (digits_before_sep == 0)
          new_len--;    /* no leading separator */

!     new_str = malloc(new_len);
!     if (!new_str)
!     {
!         fprintf(stderr, _("out of memory\n"));
!         exit(EXIT_FAILURE);
!     }

      for (i=0, j=0; ; i++, j++)
      {
--- 92,98 ----
      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++)
      {
***************
*** 167,179 ****
          if ((opt_align[i % col_count] == 'r') && strlen(*ptr) > 0 &&
              opt_numericsep != NULL && strlen(opt_numericsep) > 0)
          {
!             char *my_cell = malloc(len_with_numericsep(*ptr));

-             if (!my_cell)
-             {
-                 fprintf(stderr, _("out of memory\n"));
-                 exit(EXIT_FAILURE);
-             }
              strcpy(my_cell, *ptr);
              format_numericsep(my_cell, opt_numericsep);
              fputs(my_cell, fout);
--- 177,184 ----
          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);
***************
*** 249,261 ****
          if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
              opt_numericsep != NULL && strlen(opt_numericsep) > 0)
          {
!             char *my_cell = malloc(len_with_numericsep(*ptr));

-             if (!my_cell)
-             {
-                 fprintf(stderr, _("out of memory\n"));
-                 exit(EXIT_FAILURE);
-             }
              strcpy(my_cell, *ptr);
              format_numericsep(my_cell, opt_numericsep);
              fputs(my_cell, fout);
--- 254,261 ----
          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);
***************
*** 482,494 ****
          {
              if (strlen(*ptr) > 0 && opt_numericsep != NULL && strlen(opt_numericsep) > 0)
              {
!                 char *my_cell = malloc(cell_w[i]);

-                 if (!my_cell)
-                 {
-                     fprintf(stderr, _("out of memory\n"));
-                     exit(EXIT_FAILURE);
-                 }
                  strcpy(my_cell, *ptr);
                  format_numericsep(my_cell, opt_numericsep);
                  fprintf(fout, "%*s%s", widths[i % col_count] - cell_w[i], "", my_cell);
--- 482,489 ----
          {
              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);
***************
*** 634,645 ****
          fprintf(fout, "%s\n", title);

      /* make horizontal border */
!     divider = malloc(hwidth + dwidth + 10);
!     if (!divider)
!     {
!         fprintf(stderr, _("out of memory\n"));
!         exit(EXIT_FAILURE);
!     }
      divider[0] = '\0';
      if (opt_border == 2)
          strcat(divider, "+-");
--- 629,635 ----
          fprintf(fout, "%s\n", title);

      /* make horizontal border */
!     divider = pg_local_malloc(hwidth + dwidth + 10);
      divider[0] = '\0';
      if (opt_border == 2)
          strcat(divider, "+-");
***************
*** 661,675 ****
          {
              if (!opt_barebones)
              {
!                 char       *record_str = malloc(32);
                  size_t        record_str_len;

-                 if (!record_str)
-                 {
-                     fprintf(stderr, _("out of memory\n"));
-                     exit(EXIT_FAILURE);
-                 }
-
                  if (opt_border == 0)
                      snprintf(record_str, 32, "* Record %d", record++);
                  else
--- 651,659 ----
          {
              if (!opt_barebones)
              {
!                 char       *record_str = pg_local_malloc(32);
                  size_t        record_str_len;

                  if (opt_border == 0)
                      snprintf(record_str, 32, "* Record %d", record++);
                  else
***************
*** 709,721 ****
              fputs(" ", fout);

          {
!             char *my_cell = malloc(cell_w[i]);

-             if (!my_cell)
-             {
-                 fprintf(stderr, _("out of memory\n"));
-                 exit(EXIT_FAILURE);
-             }
              strcpy(my_cell, *ptr);
              if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
                  opt_numericsep != NULL && strlen(opt_numericsep) > 0)
--- 693,700 ----
              fputs(" ", fout);

          {
!             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)
***************
*** 855,867 ****
          else if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
                   opt_numericsep != NULL && strlen(opt_numericsep) > 0)
          {
!             char *my_cell = malloc(len_with_numericsep(*ptr));

-             if (!my_cell)
-             {
-                 fprintf(stderr, _("out of memory\n"));
-                 exit(EXIT_FAILURE);
-             }
              strcpy(my_cell, *ptr);
              format_numericsep(my_cell, opt_numericsep);
              html_escaped_print(my_cell, fout);
--- 834,841 ----
          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);
***************
*** 946,958 ****
          else if ((opt_align[i % col_count] == 'r') && strlen(*ptr) != 0 &&
              opt_numericsep != NULL && strlen(opt_numericsep) > 0)
          {
!             char *my_cell = malloc(len_with_numericsep(*ptr));

-             if (!my_cell)
-             {
-                 fprintf(stderr, _("out of memory\n"));
-                 exit(EXIT_FAILURE);
-             }
              strcpy(my_cell, *ptr);
              format_numericsep(my_cell, opt_numericsep);
              html_escaped_print(my_cell, fout);
--- 920,927 ----
          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);
***************
*** 1646,1657 ****
              exit(EXIT_FAILURE);
          }

!         footers[0] = malloc(100);
!         if (!footers[0])
!         {
!             fprintf(stderr, _("out of memory\n"));
!             exit(EXIT_FAILURE);
!         }
          if (PQntuples(result) == 1)
              snprintf(footers[0], 100, _("(1 row)"));
          else
--- 1615,1621 ----
              exit(EXIT_FAILURE);
          }

!         footers[0] = pg_local_malloc(100);
          if (PQntuples(result) == 1)
              snprintf(footers[0], 100, _("(1 row)"));
          else

Re: thousands comma numeric formatting in psql

From
Bruce Momjian
Date:
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("  ", 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("  ", 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("  ", 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("  ", 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();

Re: thousands comma numeric formatting in psql

From
Peter Eisentraut
Date:
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/

Re: thousands comma numeric formatting in psql

From
Tom Lane
Date:
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

Re: thousands comma numeric formatting in psql

From
Bruce Momjian
Date:
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 ----

Re: thousands comma numeric formatting in psql

From
Bruce Momjian
Date:
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

Re: thousands comma numeric formatting in psql

From
Eugen Nedelcu
Date:
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

Re: thousands comma numeric formatting in psql

From
Bruce Momjian
Date:
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

Re: thousands comma numeric formatting in psql

From
Eugen Nedelcu
Date:
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

Re: thousands comma numeric formatting in psql

From
Bruce Momjian
Date:
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;

Re: thousands comma numeric formatting in psql

From
Bruce Momjian
Date:
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|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"));
--- 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|tableattr|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("  ", 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("  ", 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("  ", 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("  ", 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,
!                                        opt->tuples_only, opt->numericLocale, border,
                                         opt->encoding, output);
              else
                  print_aligned_text(title, headers, cells, footers, align,
!                                    opt->tuples_only, opt->numericLocale,
                                     border, opt->encoding, output);
              break;
          case PRINT_HTML:
              if (use_expanded)
                  print_html_vertical(title, headers, cells, footers, align,
!                                     opt->tuples_only, opt->numericLocale,
                                      border, opt->tableAttr, output);
              else
                  print_html_text(title, headers, cells, footers,
!                                 align, opt->tuples_only, opt->numericLocale, border,
                                  opt->tableAttr, output);
              break;
          case PRINT_LATEX:
              if (use_expanded)
                  print_latex_vertical(title, headers, cells, footers, align,
!                                      opt->tuples_only, opt->numericLocale,
                                       border, output);
              else
                  print_latex_text(title, headers, cells, footers, align,
!                                  opt->tuples_only, opt->numericLocale,
                                   border, output);
              break;
          case PRINT_TROFF_MS:
              if (use_expanded)
                  print_troff_ms_vertical(title, headers, cells, footers, align,
!                                         opt->tuples_only, opt->numericLocale,
                                          border, output);
              else
                  print_troff_ms_text(title, headers, cells, footers, align,
!                                     opt->tuples_only, opt->numericLocale,
                                      border, output);
              break;
          default:
Index: src/bin/psql/print.h
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v
retrieving revision 1.27
diff -c -c -r1.27 print.h
*** src/bin/psql/print.h    14 Jul 2005 08:42:37 -0000    1.27
--- src/bin/psql/print.h    18 Jul 2005 19:39:03 -0000
***************
*** 40,46 ****
      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 */
--- 40,46 ----
      char       *fieldSep;        /* field separator for unaligned text mode */
      char       *recordSep;        /* record separator for unaligned text
                                   * mode */
!     bool        numericLocale;    /* locale-aware numeric units separator and
                                   *  decimal marker */
      char       *tableAttr;        /* attributes for HTML <table ...> */
      int            encoding;        /* character encoding */