thousands comma numeric formatting in psql

Started by Eugen Nedelcuover 20 years ago19 messages
#1Eugen Nedelcu
eugen@sifolt.ro

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.

#2Bruno Wolff III
bruno@wolff.to
In reply to: Eugen Nedelcu (#1)
Re: thousands comma numeric formatting in psql

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.

#3Eugen Nedelcu
eugen@sifolt.ro
In reply to: Bruno Wolff III (#2)
Re: thousands comma numeric formatting in psql

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

#4Alvaro Herrera
alvherre@surnet.cl
In reply to: Eugen Nedelcu (#3)
Re: thousands comma numeric formatting in psql

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"

#5Bruno Wolff III
bruno@wolff.to
In reply to: Eugen Nedelcu (#3)
Re: thousands comma numeric formatting in psql

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.

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#4)
Re: thousands comma numeric formatting in psql

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

#7Eugen Nedelcu
eugen@sifolt.ro
In reply to: Eugen Nedelcu (#1)
1 attachment(s)
Re: thousands comma numeric formatting in psql

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

Attachments:

thousands_comma.difftext/plain; charset=us-asciiDownload
*** ./command.c.orig	Fri Jan 24 07:23:55 2003
--- ./command.c	Fri Jun 17 12:10:12 2005
***************
*** 817,822 ****
--- 817,827 ----
  	else if (strcmp(cmd, "x") == 0)
  		success = do_pset("expanded", NULL, &pset.popt, quiet);
  
+ 	/* thousands comma patch */
+ 	/* \n -- toggle numeric formating */
+ 	else if (strcmp(cmd, "n") == 0)
+ 		success = do_pset("numeric", NULL, &pset.popt, quiet);
+ 	/* thousands comma patch */
  
  	/* \z -- list table rights (equivalent to \dp) */
  	else if (strcmp(cmd, "z") == 0)
***************
*** 1780,1785 ****
--- 1785,1802 ----
  				   : gettext("Expanded display is off.\n"));
  	}
  
+
+ 	/* thousands comma patch */
+ 	else if ((strcmp(param, "n") == 0) || (strcmp(param, "numeric") == 0))
+ 	{
+ 		popt->topt.numeric = !popt->topt.numeric;
+ 		if (!quiet)
+ 			printf(popt->topt.numeric
+ 				   ? gettext("Numeric formating is on.\n")
+ 				   : gettext("Numeric formating is off.\n"));
+ 	}
+ 	/* thousands comma patch */
+ 
  	/* null display */
  	else if (strcmp(param, "null") == 0)
  	{
*** ./help.c.orig	Thu Oct 24 04:33:50 2002
--- ./help.c	Fri Jun 17 12:42:41 2005
***************
*** 224,229 ****
--- 224,231 ----
  			ON(pset.popt.topt.expanded));
  	fprintf(output, _(" \\z [PATTERN]   list table access privileges (same as \\dp)\n"));
  	fprintf(output, _(" \\! [COMMAND]   execute command in shell or start interactive shell\n"));
+ 	fprintf(output, _(" \\n             toggle numeric formating (currently %s)\n"),
+ 			ON(pset.popt.topt.numeric));
  
  	if (output != stdout)
  	{
*** ./print.c.orig	Fri Nov  1 17:12:19 2002
--- ./print.c	Wed Jun 22 10:10:08 2005
***************
*** 19,24 ****
--- 19,114 ----
  
  #include "mbprint.h"
  
+ /* thousands comma patch */
+ # include "langinfo.h"
+ 
+ int get_nr_delim (char *);
+ int get_new_l (char *);
+ 
+ int get_nr_delim (char *my_str)
+ {
+     int old_l,dec_l,int_l,k,nr_delim;
+     old_l = strlen(my_str);
+     dec_l = (strstr(my_str, ".") ? strlen(strstr(my_str, ".")) : 0);
+     if (my_str[0] == '-')
+ 	int_l = old_l - dec_l - 1;
+     else
+ 	int_l = old_l - dec_l;
+ 	
+     k = int_l % 3;
+ 
+     if (k)
+ 	nr_delim = (int_l / 3);
+     else
+ 	nr_delim = (int_l / 3) - 1;
+ 
+     return nr_delim;
+ }
+ 
+ int get_new_l (char *my_str)
+ {
+     return (strlen(my_str) + get_nr_delim(my_str));
+ }
+ 
+ static void 
+ thousands_comma(char *my_str)
+ {
+     int i,j,k,old_l,new_l,dec_l,int_l;
+     
+     char *dec_point = nl_langinfo(__DECIMAL_POINT);
+     char *thou_sep;
+     
+     if (!strcmp(dec_point, "")) dec_point = ".";
+     if (!strcmp(dec_point, "."))
+ 	thou_sep = ",";
+     else
+ 	thou_sep = ".";
+     
+     if (my_str[0] == '-') my_str++;
+     
+     old_l = strlen(my_str);
+     dec_l = (strstr(my_str, ".") ? strlen(strstr(my_str, ".")) : 0);
+     int_l = old_l - dec_l;
+     k = int_l % 3;
+ 
+     if (k)
+ 	new_l = int_l + (int_l / 3) + dec_l;
+     else
+ 	new_l = int_l + (int_l / 3) - 1 + dec_l;
+ 
+     char new_str[new_l];
+ 
+     for (i=0,j=0;;i++,j++)
+     {
+ 	if (my_str[i] == '.')
+ 	{
+ 	    new_str[j] = *dec_point;
+ 	    new_str[j+1] = '\0';
+ 	    char *dec_value = strstr(my_str, ".");
+ 	    strcat(new_str, ++dec_value);
+ 	    break;
+ 	}
+ 
+ 	if (my_str[i] == '\0')
+ 	{
+ 	    new_str[j] = '\0';
+ 	    break;
+ 	}
+ 
+ 	if ((j - (k ? k : 3)) % 4)
+ 	    new_str[j] = my_str[i];
+ 	else
+ 	{
+ 	    new_str[j] = *thou_sep;
+ 	    i--;
+ 	}    
+     }
+         
+     strcpy(my_str, new_str);
+ }
+ 
+ /* thousands comma patch */
+ 
  /*************************/
  /* Unaligned text		 */
  /*************************/
***************
*** 26,33 ****
  
  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;
--- 116,123 ----
  
  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, bool opt_numeric,
  					 FILE *fout)
  {
  	unsigned int col_count = 0;
***************
*** 67,73 ****
  			fputs(opt_recordsep, fout);
  			need_recordsep = false;
  		}
! 		fputs(*ptr, fout);
  		if ((i + 1) % col_count)
  			fputs(opt_fieldsep, fout);
  		else
--- 157,173 ----
  			fputs(opt_recordsep, fout);
  			need_recordsep = false;
  		}
! 		/* thousands comma patch */
! 		if ((opt_align[i % col_count] == 'r') && cells[i] != "" && opt_numeric)
! 		{
! 		    char my_cell[get_new_l(cells[i])];
! 		    strcpy(my_cell, *ptr);
! 		    thousands_comma(my_cell);
! 		    fputs(my_cell, fout);
! 		} else fputs(*ptr, fout);
! 		/* thousands comma patch */
! 		
! 		/* fputs(*ptr, fout); */
  		if ((i + 1) % col_count)
  			fputs(opt_fieldsep, fout);
  		else
***************
*** 98,105 ****
  
  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;
--- 198,205 ----
  
  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, bool opt_numeric,
  						 FILE *fout)
  {
  	unsigned int col_count = 0;
***************
*** 131,137 ****
  
  		fputs(headers[i % col_count], fout);
  		fputs(opt_fieldsep, fout);
! 		fputs(*ptr, fout);
  	}
  
  	/* print footers */
--- 231,247 ----
  
  		fputs(headers[i % col_count], fout);
  		fputs(opt_fieldsep, fout);
! 		/* thousands comma patch */
! 		if ((opt_align[i % col_count] == 'r') && cells[i] != "" && opt_numeric)
! 		{
! 		    char my_cell[get_new_l(cells[i])];
! 		    strcpy(my_cell, *ptr);
! 		    thousands_comma(my_cell);
! 		    fputs(my_cell, fout);
! 		} else fputs(*ptr, fout);
! 		/* thousands comma patch */
! 
! 		/* fputs(*ptr, fout); */
  	}
  
  	/* print footers */
***************
*** 190,200 ****
  }
  
  
- 
  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,
  				   FILE *fout)
  {
--- 300,309 ----
  }
  
  
  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, bool opt_numeric,
  				   unsigned short int opt_border,
  				   FILE *fout)
  {
***************
*** 261,267 ****
  
  	for (i = 0, ptr = cells; *ptr; ptr++, i++)
  	{
! 		tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr));
  		if (tmp > widths[i % col_count])
  			widths[i % col_count] = tmp;
  		cell_w[i] = tmp;
--- 370,384 ----
  
  	for (i = 0, ptr = cells; *ptr; ptr++, i++)
  	{
! 		/* thousands comma patch */
! 		int nr_delim;
! 		if ((opt_align[i % col_count] == 'r') && cells[i] != "" && opt_numeric)
! 		    nr_delim = get_nr_delim(cells[i]);
! 		else 
! 		    nr_delim = 0;
! 		/* thousands comma patch */
! 		
! 		tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr)) + nr_delim;
  		if (tmp > widths[i % col_count])
  			widths[i % col_count] = tmp;
  		cell_w[i] = tmp;
***************
*** 343,350 ****
  		/* content */
  		if (opt_align[i % col_count] == 'r')
  		{
! 			fprintf(fout, "%*s%s",
! 					widths[i % col_count] - cell_w[i], "", cells[i]);
  		}
  		else
  		{
--- 460,475 ----
  		/* content */
  		if (opt_align[i % col_count] == 'r')
  		{
! 		    /* thousands comma patch */
! 		    if (cells[i] != "" && opt_numeric)
! 		    {
! 			char my_cell[cell_w[i]];
! 			strcpy(my_cell, cells[i]);
! 			thousands_comma(my_cell);
! 			fprintf(fout, "%*s%s", widths[i % col_count] - cell_w[i], "", my_cell);
! 		    } else
! 			fprintf(fout, "%*s%s", widths[i % col_count] - cell_w[i], "", cells[i]);
! 		    /* thousands comma patch */
  		}
  		else
  		{
***************
*** 392,399 ****
  
  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,
  					   FILE *fout)
  {
  	unsigned int col_count = 0;
--- 517,524 ----
  
  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, bool opt_numeric, unsigned short int opt_border,
  					   FILE *fout)
  {
  	unsigned int col_count = 0;
***************
*** 447,453 ****
  	/* find longest data cell */
  	for (i = 0, ptr = cells; *ptr; ptr++, i++)
  	{
! 		if ((tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr))) > dwidth)
  			dwidth = tmp;
  		cell_w[i] = tmp;
  	}
--- 572,586 ----
  	/* find longest data cell */
  	for (i = 0, ptr = cells; *ptr; ptr++, i++)
  	{
! 		/* thousands comma patch */
! 		int nr_delim;
! 		if ((opt_align[i % col_count] == 'r') && cells[i] != "" && opt_numeric)
! 		    nr_delim = get_nr_delim(cells[i]);
! 		else 
! 		    nr_delim = 0;
! 		/* thousands comma patch */
! 
! 		if ((tmp = pg_wcswidth((unsigned char *) *ptr, strlen(*ptr)) + nr_delim) > dwidth)
  			dwidth = tmp;
  		cell_w[i] = tmp;
  	}
***************
*** 532,541 ****
--- 665,687 ----
  		else
  			fputs(" ", fout);
  
+ 		/* thousands comma patch */
+ 		char my_cell[cell_w[i]];
+ 		strcpy(my_cell, *ptr);
+ 		if ((opt_align[i % col_count] == 'r') && (my_cell != "") && opt_numeric)
+ 		    thousands_comma(my_cell);
+ 		if (opt_border < 2)
+ 			fprintf(fout, "%s\n", my_cell);
+ 		else
+ 			fprintf(fout, "%-s%*s |\n", my_cell, dwidth - cell_w[i], "");
+ 		/* thousands comma patch */
+ 
+ 		/*
  		if (opt_border < 2)
  			fprintf(fout, "%s\n", *ptr);
  		else
  			fprintf(fout, "%-s%*s |\n", *ptr, dwidth - cell_w[i], "");
+ 		*/
  	}
  
  	if (opt_border == 2)
***************
*** 598,604 ****
  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)
  {
--- 744,750 ----
  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, bool opt_numeric, unsigned short int opt_border,
  				const char *opt_table_attr,
  				FILE *fout)
  {
***************
*** 646,652 ****
  														 * whitespace? */
  			fputs("&nbsp;", fout);
  		else
! 			html_escaped_print(*ptr, fout);
  		fputs("</td>\n", fout);
  
  		if ((i + 1) % col_count == 0)
--- 792,808 ----
  														 * whitespace? */
  			fputs("&nbsp;", fout);
  		else
! 			/* thousands comma patch */
! 			if ((opt_align[i % col_count] == 'r') && cells[i] != "" && opt_numeric)
! 			{
! 			    char my_cell[get_new_l(cells[i])];
! 			    strcpy(my_cell, *ptr);
! 			    thousands_comma(my_cell);
! 			    html_escaped_print(my_cell, fout);
! 			} else html_escaped_print(*ptr, fout);
! 			/* thousands comma patch */
! 
! 			/* html_escaped_print(*ptr, fout); */
  		fputs("</td>\n", fout);
  
  		if ((i + 1) % col_count == 0)
***************
*** 672,678 ****
  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)
  {
--- 828,834 ----
  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, bool opt_numeric, unsigned short int opt_border,
  					const char *opt_table_attr,
  					FILE *fout)
  {
***************
*** 718,724 ****
  														 * whitespace? */
  			fputs("&nbsp;", fout);
  		else
! 			html_escaped_print(*ptr, fout);
  		fputs("</td>\n  </tr>\n", fout);
  	}
  
--- 874,892 ----
  														 * whitespace? */
  			fputs("&nbsp;", fout);
  		else
! 		{	/* thousands comma patch */
! 			if ((opt_align[i % col_count] == 'r') && cells[i] != "" && opt_numeric)
! 			{
! 			    char my_cell[get_new_l(cells[i])];
! 			    strcpy(my_cell, *ptr);
! 			    thousands_comma(my_cell);
! 			    html_escaped_print(my_cell, fout);
! 			} else html_escaped_print(*ptr, fout);
! 		}
! 			/* thousands comma patch */
! 
! 			/* html_escaped_print(*ptr, fout); */
! 			
  		fputs("</td>\n  </tr>\n", fout);
  	}
  
***************
*** 1017,1037 ****
  	{
  		case PRINT_UNALIGNED:
  			if (opt->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 (opt->expanded)
! 				print_aligned_vertical(title, headers, cells, footers, opt->tuples_only, border, output);
  			else
! 				print_aligned_text(title, headers, cells, footers, align, opt->tuples_only, border, output);
  			break;
  		case PRINT_HTML:
  			if (opt->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:
  			if (opt->expanded)
--- 1185,1205 ----
  	{
  		case PRINT_UNALIGNED:
  			if (opt->expanded)
! 				print_unaligned_vertical(title, headers, cells, footers, align, opt->fieldSep, opt->recordSep, opt->tuples_only, opt->numeric, output);
  			else
! 				print_unaligned_text(title, headers, cells, footers, align, opt->fieldSep, opt->recordSep, opt->tuples_only, opt->numeric, output);
  			break;
  		case PRINT_ALIGNED:
  			if (opt->expanded)
! 				print_aligned_vertical(title, headers, cells, footers, align, opt->tuples_only, opt->numeric, border, output);
  			else
! 				print_aligned_text(title, headers, cells, footers, align, opt->tuples_only, opt->numeric, border, output);
  			break;
  		case PRINT_HTML:
  			if (opt->expanded)
! 				print_html_vertical(title, headers, cells, footers, align, opt->tuples_only, opt->numeric, border, opt->tableAttr, output);
  			else
! 				print_html_text(title, headers, cells, footers, align, opt->tuples_only, opt->numeric, border, opt->tableAttr, output);
  			break;
  		case PRINT_LATEX:
  			if (opt->expanded)
*** ./print.h.orig	Wed Sep  4 23:31:36 2002
--- ./print.h	Fri Jun 17 12:38:26 2005
***************
*** 26,31 ****
--- 26,34 ----
  	enum printFormat format;	/* one of the above */
  	bool		expanded;		/* expanded/vertical output (if supported
  								 * by output format) */
+ 	/* thousands comma patch */
+ 	bool		numeric;	/* numeric format */
+ 	/* thousands comma patch */
  	bool		pager;			/* use pager for output (if to stdout and
  								 * stdout is a tty) */
  	bool		tuples_only;	/* don't output headers, row counts, etc. */
*** ./startup.c.orig	Sat Oct 19 01:05:36 2002
--- ./startup.c	Fri Jun 17 12:54:43 2005
***************
*** 138,143 ****
--- 138,146 ----
  	pset.queryFout = stdout;
  	pset.popt.topt.border = 1;
  	pset.popt.topt.pager = true;
+ 	/* thousands comma patch */
+ 	pset.popt.topt.numeric = true;
+ 	/* thousands comma patch */
  	pset.popt.default_footer = true;
  
  	SetVariable(pset.vars, "VERSION", PG_VERSION_STR);
#8Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Eugen Nedelcu (#7)
1 attachment(s)
Re: thousands comma numeric formatting in psql

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

Attachments:

/bjm/difftext/plainDownload
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("&nbsp; ", 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("&nbsp; ", 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("&nbsp; ", 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("&nbsp; ", 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
#9Peter Eisentraut
peter_e@gmx.net
In reply to: Bruce Momjian (#8)
Re: thousands comma numeric formatting in psql

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/

#10Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Peter Eisentraut (#9)
Re: thousands comma numeric formatting in psql

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
#11Peter Eisentraut
peter_e@gmx.net
In reply to: Bruce Momjian (#10)
Re: [PATCHES] thousands comma numeric formatting in psql

Am Dienstag, 12. Juli 2005 05:29 schrieb Bruce Momjian:

Bruce Momjian wrote:

I have heavily modified your patch and have applied it. Instead of
using langinfo, I used a \pset variable numericsep.

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.

OK, so let's discuss what we really want here.

The original submitter wanted locale-dependent output, which seems reasonable,
because that's what locale settings are intended for.

The current state allows users to manually set the format, or actually only
one aspect of the format? Where can you set the decimal separator and the
size of the grouping (3 digits or 4 digits)? Is this capability even useful
to get localized behavior?

My other concern is that if we allow manual specification of the output format
of some data type, then eventually someone will want to specify the format of
some other data type as well, such as the date/time types. We should think
about whether we want to be consistent here.

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

#12Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Peter Eisentraut (#11)
Re: [PATCHES] thousands comma numeric formatting in psql

Peter Eisentraut wrote:

Am Dienstag, 12. Juli 2005 05:29 schrieb Bruce Momjian:

Bruce Momjian wrote:

I have heavily modified your patch and have applied it. Instead of
using langinfo, I used a \pset variable numericsep.

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.

OK, so let's discuss what we really want here.

The original submitter wanted locale-dependent output, which seems reasonable,
because that's what locale settings are intended for.

True, but there were no locale configure tests, and I don't have
langinfo, so I just made it a \pset 'string' variable. Also, when I
learned that the international system uses a space to separate
trigroups, a separate setting seemed best:

http://en.wikipedia.org/wiki/Comma_%28punctuation%29#Numbers

One thing we could do is to have a special value like 'locale' that uses
langinfo, and all other values are taken literally.

The current state allows users to manually set the format, or actually only
one aspect of the format? Where can you set the decimal separator and the
size of the grouping (3 digits or 4 digits)? Is this capability even useful
to get localized behavior?

You can't change the grouping from three, and the decimal is only be
changed by setting the numeric separator to a period. We could add a
new \pset for the decimal mark, but then do we auto-set it when the
separator is a period?

My other concern is that if we allow manual specification of the output format
of some data type, then eventually someone will want to specify the format of
some other data type as well, such as the date/time types. We should think
about whether we want to be consistent here.

We do allow MDY and DMY specification, but that controls both input and
output in the server, while this just controls psql display. It is a
good question how other settings should be handled, but I don't know the
answers. Anyone?

-- 
  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
#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#12)
Re: [PATCHES] thousands comma numeric formatting in psql

Bruce Momjian <pgman@candle.pha.pa.us> writes:

We do allow MDY and DMY specification, but that controls both input and
output in the server, while this just controls psql display. It is a
good question how other settings should be handled, but I don't know the
answers. Anyone?

This patch sounds quite a lot different from what I thought we had
agreed to, which was a way to specify that the client-side LC_NUMERIC
locale settings should be used for formatting numbers. I think the only
pset should be "locale on" or "locale not on". This business about
space is a red herring: if you want that, create a locale spec that
specifies it.

regards, tom lane

#14Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#13)
Re: [PATCHES] thousands comma numeric formatting in psql

Tom Lane wrote:

Bruce Momjian <pgman@candle.pha.pa.us> writes:

We do allow MDY and DMY specification, but that controls both input and
output in the server, while this just controls psql display. It is a
good question how other settings should be handled, but I don't know the
answers. Anyone?

This patch sounds quite a lot different from what I thought we had
agreed to, which was a way to specify that the client-side LC_NUMERIC
locale settings should be used for formatting numbers. I think the only
pset should be "locale on" or "locale not on". This business about
space is a red herring: if you want that, create a locale spec that
specifies it.

Well, how many people have langinfo? I don't. If this is what the
group wants, Someone with langinfo is going to have to code the
configure tests and convert it to a binary setting, or remove the patch.

-- 
  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
#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#14)
Re: [PATCHES] thousands comma numeric formatting in psql

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Tom Lane wrote:

This patch sounds quite a lot different from what I thought we had
agreed to, which was a way to specify that the client-side LC_NUMERIC
locale settings should be used for formatting numbers. I think the only
pset should be "locale on" or "locale not on". This business about
space is a red herring: if you want that, create a locale spec that
specifies it.

Well, how many people have langinfo? I don't.

What's langinfo got to do with it? To minimize our portability
exposure, the patch should use the same facilities that
src/backend/utils/adt/pg_locale.c already depends on, namely
setlocale() and localeconv().

regards, tom lane

#16Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#15)
Re: [PATCHES] thousands comma numeric formatting in psql

Tom Lane wrote:

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Tom Lane wrote:

This patch sounds quite a lot different from what I thought we had
agreed to, which was a way to specify that the client-side LC_NUMERIC
locale settings should be used for formatting numbers. I think the only
pset should be "locale on" or "locale not on". This business about
space is a red herring: if you want that, create a locale spec that
specifies it.

Well, how many people have langinfo? I don't.

What's langinfo got to do with it? To minimize our portability
exposure, the patch should use the same facilities that
src/backend/utils/adt/pg_locale.c already depends on, namely
setlocale() and localeconv().

The original patch included "langinfo.h" and used this call to set the
default separator:

char *dec_point = nl_langinfo(__DECIMAL_POINT);

I don't see __DECIMAL_POINT defined anywhere on the FreeBSD 4.11 box we
use for CVS.

-- 
  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
#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#16)
Re: [PATCHES] thousands comma numeric formatting in psql

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Ah, I see this now in our code:

CurrentLocaleConv.decimal_point = strdup(extlconv->decimal_point);
CurrentLocaleConv.grouping = strdup(extlconv->grouping);
CurrentLocaleConv.thousands_sep = strdup(extlconv->thousands_sep);

Is this what we should be using?

That's what I'd think. We know that localeconv() is portable --- that
code has been there for years.

Does psql use any of this now (I think no).

A quick grep shows this is the only call of localeconv() in our code.

Note: it occurs to me that those strdup's could fail in low-memory
conditions, and we're not checking. Probably the routine should
be rewritten to palloc into TopMemoryContext. Will see about it.

regards, tom lane

#18David Fetter
david@fetter.org
In reply to: Bruce Momjian (#12)
Re: [PATCHES] thousands comma numeric formatting in psql

On Tue, Jul 12, 2005 at 10:48:40AM -0400, Bruce Momjian wrote:

Peter Eisentraut wrote:

Am Dienstag, 12. Juli 2005 05:29 schrieb Bruce Momjian:

Bruce Momjian wrote:

The current state allows users to manually set the format, or
actually only one aspect of the format? Where can you set the
decimal separator and the size of the grouping (3 digits or 4
digits)? Is this capability even useful to get localized
behavior?

You can't change the grouping from three,

This makes it a non-starter, IMHO. One set of people who use 4 digits
is this little ethnic group called the Chinese.

My other concern is that if we allow manual specification of the
output format of some data type, then eventually someone will want
to specify the format of some other data type as well, such as the
date/time types. We should think about whether we want to be
consistent here.

We do allow MDY and DMY specification, but that controls both input
and output in the server, while this just controls psql display. It
is a good question how other settings should be handled, but I don't
know the answers. Anyone?

Hrm. I think we can safely tackle input and output as separate
features here. What do we do for to_char() with such separators?

Cheers,
D
--
David Fetter david@fetter.org http://fetter.org/
phone: +1 510 893 6100 mobile: +1 415 235 3778

Remember to vote!

#19Bruce Momjian
pgman@candle.pha.pa.us
In reply to: David Fetter (#18)
Re: [PATCHES] thousands comma numeric formatting in psql

David Fetter wrote:

On Tue, Jul 12, 2005 at 10:48:40AM -0400, Bruce Momjian wrote:

Peter Eisentraut wrote:

Am Dienstag, 12. Juli 2005 05:29 schrieb Bruce Momjian:

Bruce Momjian wrote:

The current state allows users to manually set the format, or
actually only one aspect of the format? Where can you set the
decimal separator and the size of the grouping (3 digits or 4
digits)? Is this capability even useful to get localized
behavior?

You can't change the grouping from three,

This makes it a non-starter, IMHO. One set of people who use 4 digits
is this little ethnic group called the Chinese.

I didn't know that.

My other concern is that if we allow manual specification of the
output format of some data type, then eventually someone will want
to specify the format of some other data type as well, such as the
date/time types. We should think about whether we want to be
consistent here.

We do allow MDY and DMY specification, but that controls both input
and output in the server, while this just controls psql display. It
is a good question how other settings should be handled, but I don't
know the answers. Anyone?

Hrm. I think we can safely tackle input and output as separate
features here. What do we do for to_char() with such separators?

Well, it isn't so much the input/output issue, but that the output
control is only in psql. Is that OK? I don't image we could ever put
input functionality in psql, only in the server.

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