Thread: NetBSD/MIPS supports dlopen

NetBSD/MIPS supports dlopen

From
Rémi Zara
Date:
Hi,

Recent version of NetBSD/MIPS support dlopen. This patch makes the
netbsd dynloader tests availability of dlopen on HAVE_DLOPEN rather
than on __mips__

Tested on NetBSD 4.99.20 on mips

I plan on registering a buildfarm member once this patch is in (and
maybe after upgrading to a more current NetBSD-current).

Regards,

Rémi Zara



Attachment

Re: NetBSD/MIPS supports dlopen

From
Alvaro Herrera
Date:
Rémi Zara wrote:
> Hi,
>
> Recent version of NetBSD/MIPS support dlopen. This patch makes the
> netbsd dynloader tests availability of dlopen on HAVE_DLOPEN rather than
> on __mips__

Applied, thanks.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Re: NetBSD/MIPS supports dlopen

From
Tom Lane
Date:
Alvaro Herrera <alvherre@commandprompt.com> writes:
> R�mi Zara wrote:
>> Recent version of NetBSD/MIPS support dlopen. This patch makes the
>> netbsd dynloader tests availability of dlopen on HAVE_DLOPEN rather than
>> on __mips__

> Applied, thanks.

Weird, I haven't seen the commit message come through here.

Anyway: (1) this should probably be back-patched; (2) please clean up
the ugly double negative here:

! #if !defined(HAVE_DLOPEN)
  #else
        dlclose(handle);
  #endif

            regards, tom lane

Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:
I've attached a patch, against current 8.4 cvs, which optionally sets a
maximum width for psql output:

# \pset format aligned-wrapped
# \pset border 2
# select * from distributors order by did;
+------+--------------------+---------------------+---------------+
| did  |        name        |        descr        | long_col_name |
+------+--------------------+---------------------+---------------+
|    1 | Food fish and wine | default             |               |
|    2 | Cat Food Heaven 2  | abcdefghijklmnopqrs !               |
|      |                    | tuvwxyz             |               |
|    3 | Cat Food Heaven 3  | default             |               |
|   10 | Lah                | default             |               |
|   12 | name               | line one            |               |
| 2892 ! short name         | short               |               |
| 8732 |                    |                     |               |
+------+--------------------+---------------------+---------------+
(8 rows)

The interactive terminal column width comes from
        char * temp = getenv("COLUMNS");
Which has the strong advantage of great simplicity and portability.  But
it may not be 1000% universal.  If $COLUMNS is not defined, the code
bails to assuming an infinitely wide terminal.

I will also backport this to Postgres 8.1, for my own use.  Though the
code is almost totally different in structure.

                         Bryce Nesbitt
                         City CarShare San Francisco

? psql
? psql_wrapping.patch
Index: command.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/bin/psql/command.c,v
retrieving revision 1.186
diff -c -r1.186 command.c
*** command.c    1 Jan 2008 19:45:55 -0000    1.186
--- command.c    5 Mar 2008 20:57:05 -0000
***************
*** 1526,1531 ****
--- 1526,1534 ----
          case PRINT_ALIGNED:
              return "aligned";
              break;
+         case PRINT_ALIGNEDWRAP:
+             return "aligned-wrapped";
+             break;
          case PRINT_HTML:
              return "html";
              break;
***************
*** 1559,1564 ****
--- 1562,1569 ----
              popt->topt.format = PRINT_UNALIGNED;
          else if (pg_strncasecmp("aligned", value, vallen) == 0)
              popt->topt.format = PRINT_ALIGNED;
+         else if (pg_strncasecmp("aligned-wrapped", value, vallen) == 0)
+             popt->topt.format = PRINT_ALIGNEDWRAP;
          else if (pg_strncasecmp("html", value, vallen) == 0)
              popt->topt.format = PRINT_HTML;
          else if (pg_strncasecmp("latex", value, vallen) == 0)
***************
*** 1567,1573 ****
              popt->topt.format = PRINT_TROFF_MS;
          else
          {
!             psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n");
              return false;
          }

--- 1572,1578 ----
              popt->topt.format = PRINT_TROFF_MS;
          else
          {
!             psql_error("\\pset: allowed formats are unaligned, aligned, aligned-wrapped, html, latex, troff-ms\n");
              return false;
          }

Index: mbprint.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/bin/psql/mbprint.c,v
retrieving revision 1.29
diff -c -r1.29 mbprint.c
*** mbprint.c    1 Jan 2008 19:45:56 -0000    1.29
--- mbprint.c    5 Mar 2008 20:57:06 -0000
***************
*** 205,211 ****
   * pg_wcssize takes the given string in the given encoding and returns three
   * values:
   *      result_width: Width in display character of longest line in string
!  *      result_hieght: Number of lines in display output
   *      result_format_size: Number of bytes required to store formatted representation of string
   */
  int
--- 205,211 ----
   * pg_wcssize takes the given string in the given encoding and returns three
   * values:
   *      result_width: Width in display character of longest line in string
!  *      result_height: Number of lines in display output
   *      result_format_size: Number of bytes required to store formatted representation of string
   */
  int
***************
*** 279,284 ****
--- 279,288 ----
      return width;
  }

+ /*
+ **  Filter out unprintable characters, companion to wcs_size.
+ **  Break input into lines (based on \n or \r).
+ */
  void
  pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
               struct lineptr * lines, int count)
***************
*** 353,364 ****
          }
          len -= chlen;
      }
!     *ptr++ = '\0';
      lines->width = linewidth;
!     lines++;
!     count--;
!     if (count > 0)
!         lines->ptr = NULL;
  }

  unsigned char *
--- 357,370 ----
          }
          len -= chlen;
      }
!     *ptr++ = '\0';          // Terminate formatted string
      lines->width = linewidth;
!     // Fill remaining array slots with null
!     while(--count) {
!         lines++;
!         lines->ptr   = NULL;
!         lines->width = 0;
!         }
  }

  unsigned char *
Index: print.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/bin/psql/print.c,v
retrieving revision 1.96
diff -c -r1.96 print.c
*** print.c    1 Jan 2008 19:45:56 -0000    1.96
--- print.c    5 Mar 2008 20:57:07 -0000
***************
*** 398,403 ****
--- 398,406 ----
  }


+ //
+ //  Prety pretty boxes around cells.
+ //
  static void
  print_aligned_text(const char *title, const char *const * headers,
                     const char *const * cells, const char *const * footers,
***************
*** 406,431 ****
  {
      bool        opt_tuples_only = opt->tuples_only;
      bool        opt_numeric_locale = opt->numericLocale;
-     unsigned short int opt_border = opt->border;
      int            encoding = opt->encoding;
      unsigned int col_count = 0;
!     unsigned int cell_count = 0;
!     unsigned int i;
      int            tmp;
!     unsigned int *widths,
!                 total_w;
!     unsigned int *heights;
!     unsigned int *format_space;
      unsigned char **format_buf;

      const char *const * ptr;

!     struct lineptr **col_lineptrs;        /* pointers to line pointer for each
!                                          * column */
!     struct lineptr *lineptr_list;        /* complete list of linepointers */

      int           *complete;        /* Array remembering which columns have
                                   * completed output */

      if (cancel_pressed)
          return;
--- 409,436 ----
  {
      bool        opt_tuples_only = opt->tuples_only;
      bool        opt_numeric_locale = opt->numericLocale;
      int            encoding = opt->encoding;
+     unsigned short int opt_border = opt->border;
+
      unsigned int col_count = 0;
!
!     unsigned int i,j;
      int            tmp;
!
!     unsigned int *width_header, *width_max, *width_wrap, *width_average;
!     unsigned int *heights, *format_space;
      unsigned char **format_buf;
+     unsigned int total_w;

      const char *const * ptr;

!     struct lineptr **col_lineptrs;    /* pointers to line pointer per column */
!     struct lineptr *lineptr_list;    /* complete list of linepointers */

      int           *complete;        /* Array remembering which columns have
                                   * completed output */
+     int         tcolumns = 0;   // Width of interactive console
+     int         rows=0;         // SQL rows in result (calculated)

      if (cancel_pressed)
          return;
***************
*** 439,446 ****

      if (col_count > 0)
      {
!         widths = pg_local_calloc(col_count, sizeof(*widths));
!         heights = pg_local_calloc(col_count, sizeof(*heights));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
          format_space = pg_local_calloc(col_count, sizeof(*format_space));
          format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
--- 444,454 ----

      if (col_count > 0)
      {
!         width_header  = pg_local_calloc(col_count, sizeof(*width_header));
!         width_average = pg_local_calloc(col_count, sizeof(*width_average));
!         width_max     = pg_local_calloc(col_count, sizeof(*width_max));
!         width_wrap    = pg_local_calloc(col_count, sizeof(*width_wrap));
!         heights    = pg_local_calloc(col_count, sizeof(*heights));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
          format_space = pg_local_calloc(col_count, sizeof(*format_space));
          format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
***************
*** 448,466 ****
      }
      else
      {
!         widths = NULL;
!         heights = NULL;
          col_lineptrs = NULL;
          format_space = NULL;
          format_buf = NULL;
          complete = NULL;
      }

!     /* count cells (rows * cols) */
!     for (ptr = cells; *ptr; ptr++)
!         cell_count++;
!
!     /* calc column widths */
      for (i = 0; i < col_count; i++)
      {
          /* Get width & height */
--- 456,473 ----
      }
      else
      {
!         width_header  = NULL;
!         width_average = NULL;
!         width_max     = NULL;
!         width_wrap    = NULL;
!         heights    = NULL;
          col_lineptrs = NULL;
          format_space = NULL;
          format_buf = NULL;
          complete = NULL;
      }

!     /* scan all column headers, find maximum width */
      for (i = 0; i < col_count; i++)
      {
          /* Get width & height */
***************
*** 468,503 ****
                      space;

          pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space);
!         if (tmp > widths[i])
!             widths[i] = tmp;
          if (height > heights[i])
              heights[i] = height;
          if (space > format_space[i])
              format_space[i] = space;
      }

      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
!         int            numeric_locale_len;
!         int            height,
!                     space;
!
!         if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
!             numeric_locale_len = additional_numeric_locale_len(*ptr);
!         else
!             numeric_locale_len = 0;

          /* Get width, ignore height */
          pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space);
!         tmp += numeric_locale_len;
!         if (tmp > widths[i % col_count])
!             widths[i % col_count] = tmp;
          if (height > heights[i % col_count])
              heights[i % col_count] = height;
          if (space > format_space[i % col_count])
              format_space[i % col_count] = space;
      }

      if (opt_border == 0)
          total_w = col_count - 1;
      else if (opt_border == 1)
--- 475,518 ----
                      space;

          pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space);
!         if (tmp > width_max[i])
!             width_max[i] = tmp;
          if (height > heights[i])
              heights[i] = height;
          if (space > format_space[i])
              format_space[i] = space;
+
+         width_header[i] = tmp;
      }

+     /* scan all rows, find maximum width */
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
!         int    height, space;

          /* Get width, ignore height */
          pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space);
!         if (opt_numeric_locale && opt_align[i % col_count] == 'r') {
!             tmp += additional_numeric_locale_len(*ptr);
!             space += additional_numeric_locale_len(*ptr);
!             }
!
!         if (tmp > width_max[i % col_count])
!             width_max[i % col_count] = tmp;
          if (height > heights[i % col_count])
              heights[i % col_count] = height;
          if (space > format_space[i % col_count])
              format_space[i % col_count] = space;
+
+         width_average[i % col_count] += tmp;
      }
+     rows=(i / col_count);
+     for (i = 0; i < col_count; i++) {
+         if( rows )
+             width_average[i % col_count] /= rows;
+         }

+     /* fiddle total display width based on border style */
      if (opt_border == 0)
          total_w = col_count - 1;
      else if (opt_border == 1)
***************
*** 505,518 ****
      else
          total_w = col_count * 3 + 1;

!     for (i = 0; i < col_count; i++)
!         total_w += widths[i];

      /*
!      * At this point: widths contains the max width of each column heights
!      * contains the max height of a cell of each column format_space contains
!      * maximum space required to store formatted string so we prepare the
!      * formatting structures
       */
      if (col_count > 0)
      {
--- 520,536 ----
      else
          total_w = col_count * 3 + 1;

!     for (i = 0; i < col_count; i++) {
!         total_w += width_max[i];
!         }

      /*
!      * At this point:
!      *  width_max[] contains the max width of each column
!      *  heights[] contains the max number of lines in each column
!      *  format_space[] contains the maximum storage space for formatting strings
!      *  total_w contains the giant width sum
!      * Now we allocate some memory...
       */
      if (col_count > 0)
      {
***************
*** 529,535 ****
              col_lineptrs[i] = lineptr;
              lineptr += heights[i];

!             format_buf[i] = pg_local_malloc(format_space[i]);

              col_lineptrs[i]->ptr = format_buf[i];
          }
--- 547,553 ----
              col_lineptrs[i] = lineptr;
              lineptr += heights[i];

!             format_buf[i] = pg_local_malloc(format_space[i]+1);

              col_lineptrs[i]->ptr = format_buf[i];
          }
***************
*** 537,555 ****
      else
          lineptr_list = NULL;

      if (opt->start_table)
      {
          /* print title */
          if (title && !opt_tuples_only)
          {
-             /* Get width & height */
              int            height;
-
              pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL);
              if (tmp >= total_w)
!                 fprintf(fout, "%s\n", title);
              else
!                 fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title);
          }

          /* print headers */
--- 555,635 ----
      else
          lineptr_list = NULL;

+
+     /*
+     **  Default the word wrap to the full width (e.g. no word wrap)
+     */
+     for (i = 0; i < col_count; i++)
+     {
+         width_wrap[i] = width_max[i];
+     }
+
+     /*
+     **  Optional optimized word wrap.
+     **  Shrink columns with a high max/avg ratio.
+     **  Slighly bias against wider columns (increasnig chance
+     **  a narrow column will fit in its cell)
+     **
+     **  Created March 2008, Bryce Nesbitt
+     */
+     if( opt->format == PRINT_ALIGNEDWRAP )
+     {
+         // If we can get the terminal width
+         char * temp = getenv("COLUMNS");
+         if( temp != NULL )
+             {
+             tcolumns = atoi(temp);
+
+             // Shink high ratio columns
+             while( total_w > tcolumns)
+             {
+                 double ratio    = 0;
+                 double temp     = 0;
+                 int    worstcol = -1;
+                 for (i = 0; i < col_count; i++)
+                     if( width_average[i] )
+                         if( width_wrap[i] > width_header[i] )
+                         {
+                             temp  =(double)width_wrap[i]/width_average[i];
+                             temp +=width_max[i] * .01; // Penalize wide columns
+                             if( temp > ratio )
+                                 ratio = temp, worstcol=i;
+                         }
+
+                 // Debugging -- please leave in source!
+                 if( true )
+                 {
+                 fprintf(fout, "Wrap ratio=");
+                 for (i = 0; i < col_count; i++)
+                     fprintf(fout, "%f %f  ",(double)width_wrap[i]/width_average[i] + width_max[i] * .01, width_max[i]
*.01); 
+                 fprintf(fout, "\n");
+                 }
+
+                 // Exit loop if we can't squeeze any more.
+                 if( worstcol < 0 )
+                     break;
+
+                 // Squeeze the worst column.  Lather, rinse, repeat
+                 width_wrap[worstcol]--;
+                 total_w--;
+             }
+         }
+     }
+
+     /*
+     ** Woo, hoo, time to output
+     */
      if (opt->start_table)
      {
          /* print title */
          if (title && !opt_tuples_only)
          {
              int            height;
              pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL);
              if (tmp >= total_w)
!                 fprintf(fout, "%s\n", title);                               // Aligned
              else
!                 fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title);  // Centered
          }

          /* print headers */
***************
*** 559,565 ****
              int            line_count;

              if (opt_border == 2)
!                 _print_horizontal_line(col_count, widths, opt_border, fout);

              for (i = 0; i < col_count; i++)
                  pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i],
heights[i]);
--- 639,645 ----
              int            line_count;

              if (opt_border == 2)
!                 _print_horizontal_line(col_count, width_wrap, opt_border, fout);

              for (i = 0; i < col_count; i++)
                  pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i],
heights[i]);
***************
*** 582,588 ****

                      if (!complete[i])
                      {
!                         nbspace = widths[i] - this_line->width;

                          /* centered */
                          fprintf(fout, "%-*s%s%-*s",
--- 662,668 ----

                      if (!complete[i])
                      {
!                         nbspace = width_wrap[i] - this_line->width;

                          /* centered */
                          fprintf(fout, "%-*s%s%-*s",
***************
*** 595,601 ****
                          }
                      }
                      else
!                         fprintf(fout, "%*s", widths[i], "");
                      if (i < col_count - 1)
                      {
                          if (opt_border == 0)
--- 675,681 ----
                          }
                      }
                      else
!                         fprintf(fout, "%*s", width_wrap[i], "");
                      if (i < col_count - 1)
                      {
                          if (opt_border == 0)
***************
*** 613,713 ****
                  fputc('\n', fout);
              }

!             _print_horizontal_line(col_count, widths, opt_border, fout);
          }
      }

      /* print cells */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         int            j;
!         int            cols_todo = col_count;
!         int            line_count; /* Number of lines output so far in row */

          if (cancel_pressed)
              break;

          for (j = 0; j < col_count; j++)
              pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);

!         line_count = 0;
          memset(complete, 0, col_count * sizeof(int));
!         while (cols_todo)
!         {
!             /* beginning of line */
!             if (opt_border == 2)
!                 fputs("| ", fout);
!             else if (opt_border == 1)
!                 fputc(' ', fout);
!
!             for (j = 0; j < col_count; j++)
!             {
!                 struct lineptr *this_line = col_lineptrs[j] + line_count;
!                 bool        finalspaces = (opt_border == 2 || j != col_count - 1);
!
!                 if (complete[j])    /* Just print spaces... */
!                 {
!                     if (finalspaces)
!                         fprintf(fout, "%*s", widths[j], "");
!                 }
!                 else
!                 {
!                     /* content */
!                     if (opt_align[j] == 'r')
!                     {
!                         if (opt_numeric_locale)
!                         {
!                             /*
!                              * Assumption: This code used only on strings
!                              * without multibyte characters, otherwise
!                              * this_line->width < strlen(this_ptr) and we get
!                              * an overflow
!                              */
!                             char       *my_cell = format_numeric_locale((char *) this_line->ptr);
!
!                             fprintf(fout, "%*s%s",
!                                     (int) (widths[i % col_count] - strlen(my_cell)), "",
!                                     my_cell);
!                             free(my_cell);
!                         }
!                         else
!                             fprintf(fout, "%*s%s",
!                                     widths[j] - this_line->width, "",
!                                     this_line->ptr);
!                     }
!                     else
!                         fprintf(fout, "%-s%*s", this_line->ptr,
!                         finalspaces ? (widths[j] - this_line->width) : 0, "");
!                     /* If at the right height, done this col */
!                     if (line_count == heights[j] - 1 || !this_line[1].ptr)
!                     {
!                         complete[j] = 1;
!                         cols_todo--;
!                     }
!                 }
!
!                 /* divider */
!                 if ((j + 1) % col_count)
!                 {
!                     if (opt_border == 0)
!                         fputc(' ', fout);
!                     else if (line_count == 0)
!                         fputs(" | ", fout);
!                     else
!                         fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':');
!                 }
!             }
!             if (opt_border == 2)
!                 fputs(" |", fout);
!             fputc('\n', fout);
!             line_count++;
!         }
!     }

      if (opt->stop_table)
      {
          if (opt_border == 2 && !cancel_pressed)
!             _print_horizontal_line(col_count, widths, opt_border, fout);

          /* print footers */
          if (footers && !opt_tuples_only && !cancel_pressed)
--- 693,821 ----
                  fputc('\n', fout);
              }

!             _print_horizontal_line(col_count, width_wrap, opt_border, fout);
          }
      }

      /* print cells */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         bool        line_todo,cols_todo;
!         int            line_count;

          if (cancel_pressed)
              break;

+         // Format each cell.  Format again, it is a numeric formatting locale (e.g. 123,456 vs. 123456)
          for (j = 0; j < col_count; j++)
+         {
              pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);
+             if (opt_numeric_locale && opt_align[j % col_count] == 'r')
+                 {
+                 char *my_cell;
+                 my_cell = format_numeric_locale((char *)col_lineptrs[j]->ptr);
+                 strcpy((char *)col_lineptrs[j]->ptr, my_cell);  // Buffer IS large enough... now
+                 free(my_cell);
+                 }
+         }

!         // Print rows to console
          memset(complete, 0, col_count * sizeof(int));
!         line_count = 0;
!         line_todo  = true;
!         while (line_todo)
!         {
!             cols_todo  = true;
!             while (cols_todo)
!             {
!                 char    border_cell = '*';
!                 cols_todo = false;
!
!                 /* left border */
!                 if (opt_border == 2)
!                     fputs("| ", fout);
!                 else if (opt_border == 1)
!                     fputc(' ', fout);
!
!                 /* for each column */
!                 for (j = 0; j < col_count; j++)
!                 {
!                     struct lineptr *this_line = &col_lineptrs[j][line_count];
!
!                     if( heights[j] <= line_count ) { // Blank column content
!                         fprintf(fout, "%*s", width_wrap[j], "");
!                         border_cell = '|';
!                         }
!                     else if (opt_align[j] == 'r')    // Right aligned cell
!                     {
!                         int    strlen_remaining   = strlen((char *)this_line->ptr+complete[j]);
!                         if( strlen_remaining > width_wrap[j] )
!                         {
!                             fprintf(fout, "%.*s", width_wrap[j], this_line->ptr+complete[j] );
!                             complete[j] += width_wrap[j];   // We've done THIS much
!                             cols_todo = true;               // And there is more to do...
!                             border_cell = ':';
!                         } else
!                         {
!                             fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, "");
!                             fprintf(fout, "%-s", this_line->ptr + complete[j] );
!                             complete[j] += strlen_remaining;
!                             border_cell = '|';
!                         }
!                     }
!                     else    // Left aligned cell
!                     {
!                         int    strlen_remaining   = strlen((char *)this_line->ptr+complete[j]);
!                         if( strlen_remaining > width_wrap[j] )
!                         {
!                             fprintf(fout, "%.*s", width_wrap[j], this_line->ptr+complete[j] );
!                             complete[j] += width_wrap[j];   // We've done THIS much
!                             cols_todo = true;               // And there is more to do...
!                             border_cell = ':';
!                         } else
!                         {
!                             fprintf(fout, "%-s", this_line->ptr + complete[j] );
!                             fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, "");
!                             complete[j] += strlen_remaining;
!                             border_cell = '|';
!                         }
!                     }
!
!                     /* print a divider, middle of columns only */
!                     if ((j + 1) % col_count)
!                     {
!                         if (opt_border == 0)
!                             fputc(' ', fout);
!                         else
!                             fprintf(fout, " %c ", border_cell);
!                     }
!                 }
!
!                 /* end of row border */
!                 if (opt_border == 2)
!                     fprintf(fout, " %c", border_cell);
!                 fputc('\n', fout);
!             }
!
!             // Check if any columns have line continuations (due to \n in the cell)
!             line_count++;
!             line_todo = false;
!             for (j = 0; j < col_count; j++)
!             {
!                 if( line_count < heights[j]) {
!                     if( col_lineptrs[j][line_count].ptr ) {
!                         line_todo = true;
!                         complete[j]=0;
!                         }
!                     }
!             }
!         }
!     }

      if (opt->stop_table)
      {
          if (opt_border == 2 && !cancel_pressed)
!             _print_horizontal_line(col_count, width_wrap, opt_border, fout);

          /* print footers */
          if (footers && !opt_tuples_only && !cancel_pressed)
***************
*** 724,730 ****
      }

      /* clean up */
!     free(widths);
      free(heights);
      free(col_lineptrs);
      free(format_space);
--- 832,841 ----
      }

      /* clean up */
!     free(width_header);
!     free(width_average);
!     free(width_max);
!     free(width_wrap);
      free(heights);
      free(col_lineptrs);
      free(format_space);
***************
*** 1881,1886 ****
--- 1992,1998 ----
                                       opt, output);
              break;
          case PRINT_ALIGNED:
+         case PRINT_ALIGNEDWRAP:
              if (opt->expanded)
                  print_aligned_vertical(title, headers, cells, footers, align,
                                         opt, output);
Index: print.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/bin/psql/print.h,v
retrieving revision 1.35
diff -c -r1.35 print.h
*** print.h    1 Jan 2008 19:45:56 -0000    1.35
--- print.h    5 Mar 2008 20:57:07 -0000
***************
*** 21,26 ****
--- 21,27 ----
      PRINT_NOTHING = 0,            /* to make sure someone initializes this */
      PRINT_UNALIGNED,
      PRINT_ALIGNED,
+     PRINT_ALIGNEDWRAP,
      PRINT_HTML,
      PRINT_LATEX,
      PRINT_TROFF_MS

Re: NetBSD/MIPS supports dlopen

From
Alvaro Herrera
Date:
Tom Lane wrote:
> Alvaro Herrera <alvherre@commandprompt.com> writes:
> > R�mi Zara wrote:
> >> Recent version of NetBSD/MIPS support dlopen. This patch makes the
> >> netbsd dynloader tests availability of dlopen on HAVE_DLOPEN rather than
> >> on __mips__
>
> > Applied, thanks.
>
> Anyway: (1) this should probably be back-patched; (2) please clean up
> the ugly double negative here:

Both done -- I backpatched all the way down to 7.4 (and later I noticed
that the only 7.3 BF members are NetBSD).

> Weird, I haven't seen the commit message come through here.

Yeah, that's strange -- the (2) commit message got to me, but this one
hasn't.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

Re: NetBSD/MIPS supports dlopen

From
Tom Lane
Date:
Alvaro Herrera <alvherre@commandprompt.com> writes:
> Tom Lane wrote:
>> Weird, I haven't seen the commit message come through here.

> Yeah, that's strange -- the (2) commit message got to me, but this one
> hasn't.

Moderation filter got it for some reason?  None of the back-patch
commits came through either, so there's something going on there...

            regards, tom lane

Re: NetBSD/MIPS supports dlopen

From
Andrew Dunstan
Date:

Alvaro Herrera wrote:
> Both done -- I backpatched all the way down to 7.4 (and later I noticed
> that the only 7.3 BF members are NetBSD).
>
>

Haven't we declared 7.3 at EOL anyway?

cheers

andrew

Re: NetBSD/MIPS supports dlopen

From
Alvaro Herrera
Date:
Tom Lane wrote:
> Alvaro Herrera <alvherre@commandprompt.com> writes:
> > Tom Lane wrote:
> >> Weird, I haven't seen the commit message come through here.
>
> > Yeah, that's strange -- the (2) commit message got to me, but this one
> > hasn't.
>
> Moderation filter got it for some reason?

Hmm, not moderation, because I am a moderator and didn't get it.

> None of the back-patch
> commits came through either, so there's something going on there...

Perhaps it's the fact that I used Rémi's name with the accent.  I'll
check Majordomo logs if it lets me.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Re: NetBSD/MIPS supports dlopen

From
Alvaro Herrera
Date:
Andrew Dunstan wrote:
>
>
> Alvaro Herrera wrote:
>> Both done -- I backpatched all the way down to 7.4 (and later I noticed
>> that the only 7.3 BF members are NetBSD).
>
> Haven't we declared 7.3 at EOL anyway?

That's why I didn't backpatch it there.  But if that's the case, why are
we still reporting 7.3 in the buildfarm status page?

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Re: NetBSD/MIPS supports dlopen

From
Alvaro Herrera
Date:
Alvaro Herrera wrote:
> Tom Lane wrote:
> > Alvaro Herrera <alvherre@commandprompt.com> writes:
> > > Tom Lane wrote:
> > >> Weird, I haven't seen the commit message come through here.
> >
> > > Yeah, that's strange -- the (2) commit message got to me, but this one
> > > hasn't.

> > None of the back-patch
> > commits came through either, so there's something going on there...
>
> Perhaps it's the fact that I used Rémi's name with the accent.  I'll
> check Majordomo logs if it lets me.

I checked the Majordomo logs and there's nothing about those patches.
I do see one message with the "Subject: pgsql: Clean up double negative,
per Tom Lane." line.  A message held for moderation shows up in the logs
with a "stall" status.  So these messages where chopped _before_ they
got into Majordomo at all ...

Perhaps a bug in the script that sends the email?

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Re: NetBSD/MIPS supports dlopen

From
Magnus Hagander
Date:
Alvaro Herrera wrote:
> Alvaro Herrera wrote:
>> Tom Lane wrote:
>>> Alvaro Herrera <alvherre@commandprompt.com> writes:
>>>> Tom Lane wrote:
>>>>> Weird, I haven't seen the commit message come through here.
>>>> Yeah, that's strange -- the (2) commit message got to me, but this one
>>>> hasn't.
>
>>> None of the back-patch
>>> commits came through either, so there's something going on there...
>> Perhaps it's the fact that I used Rémi's name with the accent.  I'll
>> check Majordomo logs if it lets me.
>
> I checked the Majordomo logs and there's nothing about those patches.
> I do see one message with the "Subject: pgsql: Clean up double negative,
> per Tom Lane." line.  A message held for moderation shows up in the logs
> with a "stall" status.  So these messages where chopped _before_ they
> got into Majordomo at all ...
>
> Perhaps a bug in the script that sends the email?

I see a bunch of emails from you leaving the system today. My first
guess for problem location would be the antispam, but before we rule out
the sender completely - at exactly what time was the commit(s) that made
it through made, and at what time was the commit(s) that didn't make it
through? In GMT time, please :-)

//Magnus


Re: NetBSD/MIPS supports dlopen

From
Andrew Dunstan
Date:

Alvaro Herrera wrote:
> Andrew Dunstan wrote:
>
>> Alvaro Herrera wrote:
>>
>>> Both done -- I backpatched all the way down to 7.4 (and later I noticed
>>> that the only 7.3 BF members are NetBSD).
>>>
>> Haven't we declared 7.3 at EOL anyway?
>>
>
> That's why I didn't backpatch it there.  But if that's the case, why are
> we still reporting 7.3 in the buildfarm status page?
>
>

Because until a couple of weeks ago those two machines were still
reporting that branch. When they are 30 days old the reports will drop
off the page. (It looks like salamander has stopped altogether, which
Tom mentioned the other day would distress him some.)

cheers

andrew

Re: NetBSD/MIPS supports dlopen

From
Alvaro Herrera
Date:
Magnus Hagander wrote:
> Alvaro Herrera wrote:

>> I checked the Majordomo logs and there's nothing about those patches.
>> I do see one message with the "Subject: pgsql: Clean up double negative,
>> per Tom Lane." line.  A message held for moderation shows up in the logs
>> with a "stall" status.  So these messages where chopped _before_ they
>> got into Majordomo at all ...
>>
>> Perhaps a bug in the script that sends the email?
>
> I see a bunch of emails from you leaving the system today. My first
> guess for problem location would be the antispam, but before we rule out
> the sender completely - at exactly what time was the commit(s) that made
> it through made, and at what time was the commit(s) that didn't make it
> through? In GMT time, please :-)

Huh, I have zero idea and I had already closed the windows.  So, from
the CVS logs:

revision 1.23
date: 2008/03/05 19:42:11;  author: alvherre;  state: Exp;  lines: +4 -4
Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.
...
revision 1.12.4.1
date: 2008/03/05 21:20:49;  author: alvherre;  state: Exp;  lines: +3 -4
Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.
----------------------------
revision 1.16.4.1
date: 2008/03/05 21:20:48;  author: alvherre;  state: Exp;  lines: +3 -4
Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.
----------------------------
revision 1.17.2.1
date: 2008/03/05 21:20:49;  author: alvherre;  state: Exp;  lines: +3 -4
Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.
----------------------------
revision 1.19.2.1
date: 2008/03/05 21:20:48;  author: alvherre;  state: Exp;  lines: +4 -5
Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.
----------------------------
revision 1.22.2.1
date: 2008/03/05 21:20:47;  author: alvherre;  state: Exp;  lines: +4 -5
Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.

These are all GMT AFAICT.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Re: NetBSD/MIPS supports dlopen

From
Magnus Hagander
Date:
On Wed, Mar 05, 2008 at 07:38:21PM -0300, Alvaro Herrera wrote:
> Magnus Hagander wrote:
> > Alvaro Herrera wrote:
>
> >> I checked the Majordomo logs and there's nothing about those patches.
> >> I do see one message with the "Subject: pgsql: Clean up double negative,
> >> per Tom Lane." line.  A message held for moderation shows up in the logs
> >> with a "stall" status.  So these messages where chopped _before_ they
> >> got into Majordomo at all ...
> >>
> >> Perhaps a bug in the script that sends the email?
> >
> > I see a bunch of emails from you leaving the system today. My first
> > guess for problem location would be the antispam, but before we rule out
> > the sender completely - at exactly what time was the commit(s) that made
> > it through made, and at what time was the commit(s) that didn't make it
> > through? In GMT time, please :-)
>
> Huh, I have zero idea and I had already closed the windows.  So, from
> the CVS logs:

This is enough - I just wanted to be sure which commits we talked about.


> revision 1.23
> date: 2008/03/05 19:42:11;  author: alvherre;  state: Exp;  lines: +4 -4
> Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.

19:42:11 I have one email going out to pgsql-committers from alvherre.

21:14:10 I have another, that doesn't match any of these commits. Did you
make anotherone that you didn't include in this list?

> ...
> revision 1.12.4.1
> date: 2008/03/05 21:20:49;  author: alvherre;  state: Exp;  lines: +3 -4
> Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.
> ----------------------------
> revision 1.16.4.1
> date: 2008/03/05 21:20:48;  author: alvherre;  state: Exp;  lines: +3 -4
> Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.
> ----------------------------
> revision 1.17.2.1
> date: 2008/03/05 21:20:49;  author: alvherre;  state: Exp;  lines: +3 -4
> Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.
> ----------------------------
> revision 1.19.2.1
> date: 2008/03/05 21:20:48;  author: alvherre;  state: Exp;  lines: +4 -5
> Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.
> ----------------------------
> revision 1.22.2.1
> date: 2008/03/05 21:20:47;  author: alvherre;  state: Exp;  lines: +4 -5
> Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.

21:20:47 I count 1.
21:20:48 I count 2.
21:20:49 I count 2.

All these mails have been acknowledged as received by svr1.postgresql.org.-

So the script that sends them out is working properly. I'm back at blaming
the antispam for eating them before they got out to the list. Especially
since you didn't get a boucne (I assume you would've noticed if you did)

//Magnus

Re: NetBSD/MIPS supports dlopen

From
Alvaro Herrera
Date:
Magnus Hagander wrote:
> On Wed, Mar 05, 2008 at 07:38:21PM -0300, Alvaro Herrera wrote:

> > revision 1.23
> > date: 2008/03/05 19:42:11;  author: alvherre;  state: Exp;  lines: +4 -4
> > Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara.
>
> 19:42:11 I have one email going out to pgsql-committers from alvherre.
>
> 21:14:10 I have another, that doesn't match any of these commits. Did you
> make anotherone that you didn't include in this list?

Correct.

> So the script that sends them out is working properly. I'm back at blaming
> the antispam for eating them before they got out to the list. Especially
> since you didn't get a boucne (I assume you would've noticed if you did)

Hmm, so most likely they are in Maia's hands and only Marc can rescue
them.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

Re: Proposed patch - psql wraps at window width

From
Alvaro Herrera
Date:
Bryce Nesbitt wrote:
> I've attached a patch, against current 8.4 cvs, which optionally sets a
> maximum width for psql output:

I have added this patch to the May commitfest queue,

http://wiki.postgresql.org/wiki/CommitFest:May

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Re: Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
Bryce Nesbitt wrote:
> I've attached a patch, against current 8.4 cvs, which optionally sets a
> maximum width for psql output:
>
> # \pset format aligned-wrapped
> # \pset border 2
> # select * from distributors order by did;
> +------+--------------------+---------------------+---------------+
> | did  |        name        |        descr        | long_col_name |
> +------+--------------------+---------------------+---------------+
> |    1 | Food fish and wine | default             |               |
> |    2 | Cat Food Heaven 2  | abcdefghijklmnopqrs !               |
> |      |                    | tuvwxyz             |               |
> |    3 | Cat Food Heaven 3  | default             |               |
> |   10 | Lah                | default             |               |
> |   12 | name               | line one            |               |
> | 2892 ! short name         | short               |               |
> | 8732 |                    |                     |               |
> +------+--------------------+---------------------+---------------+
> (8 rows)
>
> The interactive terminal column width comes from
>         char * temp = getenv("COLUMNS");
> Which has the strong advantage of great simplicity and portability.  But
> it may not be 1000% universal.  If $COLUMNS is not defined, the code
> bails to assuming an infinitely wide terminal.
>
> I will also backport this to Postgres 8.1, for my own use.  Though the
> code is almost totally different in structure.

I spent time reviewing your patch --- quite impressive.  I have attached
and updated version with mostly stylistic changes.

In testing I found the regression tests were failing because of a divide
by zero error (fixed), and a missing case where the column delimiter has
to be ":".  In fact I now see all your line continuation cases using ":"
rather than "!".  It actually looks better --- "!" was too close to "|"
to be easily recognized.  (Did you update your patch to use ":".  I
didn't see "!" in your patch.)

I have added an XXX comment questioning whether the loop to find the
column to wrap is inefficient because it potentially loops over the
length of the longest column and for each character loops over the
number of columns.  Not sure if that is a problem.

I checked the use of COLUMNS and it seems bash updates the environment
variable when a window is resized.  I added ioctl(TIOCGWINSZ) if COLUMNS
isn't set.  We already had a call in print.c for detecting the
number of rows on the screen to determine if the pager should
be used.  Seems COLUMNS should take precedence over ioctl(), right?
I don't think Win32 supports that ioctl(), does it?

I added some comments and clarified some variable names.  I also renamed
the option to a shorter "wrapped".  I added documentation too.

For testers compare:

    \df

with:

    \pset format wrap
    \df

Impressive!

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +
Index: doc/src/sgml/ref/psql-ref.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v
retrieving revision 1.199
diff -c -c -r1.199 psql-ref.sgml
*** doc/src/sgml/ref/psql-ref.sgml    30 Mar 2008 18:10:20 -0000    1.199
--- doc/src/sgml/ref/psql-ref.sgml    17 Apr 2008 02:45:38 -0000
***************
*** 1513,1519 ****
            <listitem>
            <para>
            Sets the output format to one of <literal>unaligned</literal>,
!           <literal>aligned</literal>, <literal>html</literal>,
            <literal>latex</literal>, or <literal>troff-ms</literal>.
            Unique abbreviations are allowed.  (That would mean one letter
            is enough.)
--- 1513,1520 ----
            <listitem>
            <para>
            Sets the output format to one of <literal>unaligned</literal>,
!           <literal>aligned</literal>, <literal>wrapped</literal>,
!           <literal>html</literal>,
            <literal>latex</literal>, or <literal>troff-ms</literal>.
            Unique abbreviations are allowed.  (That would mean one letter
            is enough.)
***************
*** 1525,1531 ****
            is intended to create output that might be intended to be read
            in by other programs (tab-separated, comma-separated).
            <quote>Aligned</quote> mode is the standard, human-readable,
!           nicely formatted text output that is default. The
            <quote><acronym>HTML</acronym></quote> and
            <quote>LaTeX</quote> modes put out tables that are intended to
            be included in documents using the respective mark-up
--- 1526,1535 ----
            is intended to create output that might be intended to be read
            in by other programs (tab-separated, comma-separated).
            <quote>Aligned</quote> mode is the standard, human-readable,
!           nicely formatted text output that is default.
!           <quote>Wrapped</quote> is like <literal>aligned</> but wraps
!           the output to fit the screen's width, based on the environment
!           variable <envar>COLUMNS</> or the autodetected width. The
            <quote><acronym>HTML</acronym></quote> and
            <quote>LaTeX</quote> modes put out tables that are intended to
            be included in documents using the respective mark-up
***************
*** 2708,2713 ****
--- 2712,2730 ----

    <variablelist>
     <varlistentry>
+     <term><envar>COLUMNS</envar></term>
+
+     <listitem>
+      <para>
+       The character width to wrap output in <literal>wrapped</> format
+       mode.  Many shells automatically update <envar>COLUMNS</> when
+       a window is resized.  If not set the screen width is automatically
+       detected, if possible.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
      <term><envar>PAGER</envar></term>

      <listitem>
Index: src/bin/psql/command.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v
retrieving revision 1.186
diff -c -c -r1.186 command.c
*** src/bin/psql/command.c    1 Jan 2008 19:45:55 -0000    1.186
--- src/bin/psql/command.c    17 Apr 2008 02:45:38 -0000
***************
*** 1526,1531 ****
--- 1526,1534 ----
          case PRINT_ALIGNED:
              return "aligned";
              break;
+         case PRINT_WRAP:
+             return "wrapped";
+             break;
          case PRINT_HTML:
              return "html";
              break;
***************
*** 1559,1564 ****
--- 1562,1569 ----
              popt->topt.format = PRINT_UNALIGNED;
          else if (pg_strncasecmp("aligned", value, vallen) == 0)
              popt->topt.format = PRINT_ALIGNED;
+         else if (pg_strncasecmp("wrapped", value, vallen) == 0)
+             popt->topt.format = PRINT_WRAP;
          else if (pg_strncasecmp("html", value, vallen) == 0)
              popt->topt.format = PRINT_HTML;
          else if (pg_strncasecmp("latex", value, vallen) == 0)
***************
*** 1567,1573 ****
              popt->topt.format = PRINT_TROFF_MS;
          else
          {
!             psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n");
              return false;
          }

--- 1572,1578 ----
              popt->topt.format = PRINT_TROFF_MS;
          else
          {
!             psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
              return false;
          }

Index: src/bin/psql/mbprint.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/mbprint.c,v
retrieving revision 1.30
diff -c -c -r1.30 mbprint.c
*** src/bin/psql/mbprint.c    16 Apr 2008 18:18:00 -0000    1.30
--- src/bin/psql/mbprint.c    17 Apr 2008 02:45:38 -0000
***************
*** 279,284 ****
--- 279,288 ----
      return width;
  }

+ /*
+  *  Filter out unprintable characters, companion to wcs_size.
+  *  Break input into lines (based on \n or \r).
+  */
  void
  pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
               struct lineptr * lines, int count)
***************
*** 353,364 ****
          }
          len -= chlen;
      }
!     *ptr++ = '\0';
      lines->width = linewidth;
!     lines++;
!     count--;
!     if (count > 0)
          lines->ptr = NULL;
  }

  unsigned char *
--- 357,373 ----
          }
          len -= chlen;
      }
!     *ptr++ = '\0';            /* Terminate formatted string */
!
      lines->width = linewidth;
!
!     /* Fill remaining array slots with nulls */
!     while (--count)
!     {
!         lines++;
          lines->ptr = NULL;
+         lines->width = 0;
+     }
  }

  unsigned char *
Index: src/bin/psql/print.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v
retrieving revision 1.97
diff -c -c -r1.97 print.c
*** src/bin/psql/print.c    27 Mar 2008 03:57:34 -0000    1.97
--- src/bin/psql/print.c    17 Apr 2008 02:45:38 -0000
***************
*** 396,401 ****
--- 396,404 ----
  }


+ /*
+  *    Prety pretty boxes around cells.
+  */
  static void
  print_aligned_text(const char *title, const char *const * headers,
                     const char *const * cells, const char *const * footers,
***************
*** 404,429 ****
  {
      bool        opt_tuples_only = opt->tuples_only;
      bool        opt_numeric_locale = opt->numericLocale;
-     unsigned short int opt_border = opt->border;
      int            encoding = opt->encoding;
!     unsigned int col_count = 0;
!     unsigned int cell_count = 0;
!     unsigned int i;
!     int            tmp;
!     unsigned int *widths,
!                 total_w;
!     unsigned int *heights;
!     unsigned int *format_space;
      unsigned char **format_buf;

      const char *const * ptr;

!     struct lineptr **col_lineptrs;        /* pointers to line pointer for each
!                                          * column */
      struct lineptr *lineptr_list;        /* complete list of linepointers */

      int           *complete;        /* Array remembering which columns have
                                   * completed output */

      if (cancel_pressed)
          return;
--- 407,437 ----
  {
      bool        opt_tuples_only = opt->tuples_only;
      bool        opt_numeric_locale = opt->numericLocale;
      int            encoding = opt->encoding;
!     unsigned short int opt_border = opt->border;
!
!     unsigned int col_count = 0, cell_count = 0;
!
!     unsigned int i,
!                 j;
!
!     unsigned int *width_header,
!                *width_max,
!                *width_wrap,
!                *width_average;
!     unsigned int *heights,
!                *format_space;
      unsigned char **format_buf;
+     unsigned int width_total;

      const char *const * ptr;

!     struct lineptr **col_lineptrs;        /* pointers to line pointer per column */
      struct lineptr *lineptr_list;        /* complete list of linepointers */

      int           *complete;        /* Array remembering which columns have
                                   * completed output */
+     int            tcolumns = 0;    /* Width of interactive console */

      if (cancel_pressed)
          return;
***************
*** 437,443 ****

      if (col_count > 0)
      {
!         widths = pg_local_calloc(col_count, sizeof(*widths));
          heights = pg_local_calloc(col_count, sizeof(*heights));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
          format_space = pg_local_calloc(col_count, sizeof(*format_space));
--- 445,454 ----

      if (col_count > 0)
      {
!         width_header = pg_local_calloc(col_count, sizeof(*width_header));
!         width_average = pg_local_calloc(col_count, sizeof(*width_average));
!         width_max = pg_local_calloc(col_count, sizeof(*width_max));
!         width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap));
          heights = pg_local_calloc(col_count, sizeof(*heights));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
          format_space = pg_local_calloc(col_count, sizeof(*format_space));
***************
*** 446,452 ****
      }
      else
      {
!         widths = NULL;
          heights = NULL;
          col_lineptrs = NULL;
          format_space = NULL;
--- 457,466 ----
      }
      else
      {
!         width_header = NULL;
!         width_average = NULL;
!         width_max = NULL;
!         width_wrap = NULL;
          heights = NULL;
          col_lineptrs = NULL;
          format_space = NULL;
***************
*** 454,533 ****
          complete = NULL;
      }

!     /* count cells (rows * cols) */
!     for (ptr = cells; *ptr; ptr++)
!         cell_count++;
!
!     /* calc column widths */
      for (i = 0; i < col_count; i++)
      {
          /* Get width & height */
!         int            height,
                      space;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space);
!         if (tmp > widths[i])
!             widths[i] = tmp;
          if (height > heights[i])
              heights[i] = height;
          if (space > format_space[i])
              format_space[i] = space;
      }

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

-         if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
-             numeric_locale_len = additional_numeric_locale_len(*ptr);
-         else
-             numeric_locale_len = 0;
-
          /* Get width, ignore height */
!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space);
!         tmp += numeric_locale_len;
!         if (tmp > widths[i % col_count])
!             widths[i % col_count] = tmp;
          if (height > heights[i % col_count])
              heights[i % col_count] = height;
          if (space > format_space[i % col_count])
              format_space[i % col_count] = space;
      }

      if (opt_border == 0)
!         total_w = col_count - 1;
      else if (opt_border == 1)
!         total_w = col_count * 3 - 1;
      else
!         total_w = col_count * 3 + 1;

      for (i = 0; i < col_count; i++)
!         total_w += widths[i];

      /*
!      * At this point: widths contains the max width of each column heights
!      * contains the max height of a cell of each column format_space contains
!      * maximum space required to store formatted string so we prepare the
!      * formatting structures
       */
      if (col_count > 0)
      {
!         int            heights_total = 0;
          struct lineptr *lineptr;

          for (i = 0; i < col_count; i++)
!             heights_total += heights[i];

!         lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list));

          for (i = 0; i < col_count; i++)
          {
              col_lineptrs[i] = lineptr;
              lineptr += heights[i];

!             format_buf[i] = pg_local_malloc(format_space[i]);

              col_lineptrs[i]->ptr = format_buf[i];
          }
--- 468,560 ----
          complete = NULL;
      }

!     /* scan all column headers, find maximum width */
      for (i = 0; i < col_count; i++)
      {
          /* Get width & height */
!         int            width,
!                     height,
                      space;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &space);
!         if (width > width_max[i])
!             width_max[i] = width;
          if (height > heights[i])
              heights[i] = height;
          if (space > format_space[i])
              format_space[i] = space;
+
+         width_header[i] = width;
      }

!     /* scan all rows, find maximum width, compute cell_count */
!     for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++)
      {
!         int            width,
!                     height,
                      space;

          /* Get width, ignore height */
!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &space);
!         if (opt_numeric_locale && opt_align[i % col_count] == 'r')
!         {
!             width += additional_numeric_locale_len(*ptr);
!             space += additional_numeric_locale_len(*ptr);
!         }
!
!         if (width > width_max[i % col_count])
!             width_max[i % col_count] = width;
          if (height > heights[i % col_count])
              heights[i % col_count] = height;
          if (space > format_space[i % col_count])
              format_space[i % col_count] = space;
+
+         width_average[i % col_count] += width;
+     }
+
+     /* If we have rows, compute average */
+     if (col_count != 0 && cell_count != 0)
+     {
+         int rows = cell_count / col_count;
+
+         for (i = 0; i < col_count; i++)
+             width_average[i % col_count] /= rows;
      }

+     /* adjust the total display width based on border style */
      if (opt_border == 0)
!         width_total = col_count - 1;
      else if (opt_border == 1)
!         width_total = col_count * 3 - 1;
      else
!         width_total = col_count * 3 + 1;

      for (i = 0; i < col_count; i++)
!         width_total += width_max[i];

      /*
!      * At this point: width_max[] contains the max width of each column,
!      * heights[] contains the max number of lines in each column,
!      * format_space[] contains the maximum storage space for formatting
!      * strings, width_total contains the giant width sum.  Now we allocate
!      * some memory...
       */
      if (col_count > 0)
      {
!         int            height_total = 0;
          struct lineptr *lineptr;

          for (i = 0; i < col_count; i++)
!             height_total += heights[i];

!         lineptr = lineptr_list = pg_local_calloc(height_total, sizeof(*lineptr_list));

          for (i = 0; i < col_count; i++)
          {
              col_lineptrs[i] = lineptr;
              lineptr += heights[i];

!             format_buf[i] = pg_local_malloc(format_space[i] + 1);

              col_lineptrs[i]->ptr = format_buf[i];
          }
***************
*** 535,553 ****
      else
          lineptr_list = NULL;

      if (opt->start_table)
      {
          /* print title */
          if (title && !opt_tuples_only)
          {
!             /* Get width & height */
!             int            height;

!             pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL);
!             if (tmp >= total_w)
!                 fprintf(fout, "%s\n", title);
              else
!                 fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title);
          }

          /* print headers */
--- 562,650 ----
      else
          lineptr_list = NULL;

+
+     /* Default word wrap to the full width, i.e. no word wrap */
+     for (i = 0; i < col_count; i++)
+         width_wrap[i] = width_max[i];
+
+     /*
+      * Optional optimized word wrap. Shrink columns with a high max/avg ratio.
+      * Slighly bias against wider columns (increases chance a narrow column
+      * will fit in its cell)
+      */
+     if (opt->format == PRINT_WRAP)
+     {
+         /* If we can get the terminal width */
+         char       *env = getenv("COLUMNS");
+
+         if (env != NULL)
+             tcolumns = atoi(env);
+ #ifdef TIOCGWINSZ
+         else
+         {
+             struct winsize screen_size;
+
+             if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
+                 tcolumns = screen_size.ws_col;
+         }
+ #endif
+
+         if (tcolumns > 0)
+         {
+             /* Shink high ratio columns */
+             while (width_total > tcolumns)
+             {
+                 double        ratio = 0;
+                 double        curr_ratio = 0;
+                 int            worst_col = -1;
+
+                 /*
+                  *    Find column that has the highest ratio of its maximum
+                  *    width compared to its average width.  This tells us which
+                  *    column will produce the fewest wrapped values if shortened.
+                  *    width_wrap starts as equal to width_max.
+                  */
+                 for (i = 0; i < col_count; i++)
+                     if (width_average[i] && width_wrap[i] > width_header[i])
+                     {
+                         curr_ratio = (double) width_wrap[i] / width_average[i];
+                         curr_ratio += width_max[i] * 0.01;        /* Penalize wide columns */
+                         if (curr_ratio > ratio)
+                         {
+                             ratio = curr_ratio;
+                             worst_col = i;
+                         }
+                     }
+
+                 /* Exit loop if we can't squeeze any more. */
+                 if (worst_col < 0)
+                     break;
+
+                 /* Squeeze the worst column.  Lather, rinse, repeat */
+                 /*
+                  *    XXX This only reduces one character at a time so might
+                  *    be inefficient for very long rows.
+                  */
+                 width_wrap[worst_col]--;
+                 width_total--;
+             }
+         }
+     }
+
+     /* time to output */
      if (opt->start_table)
      {
          /* print title */
          if (title && !opt_tuples_only)
          {
!             int            width,
!                         height;

!             pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL);
!             if (width >= width_total)
!                 fprintf(fout, "%s\n", title);    /* Aligned */
              else
!                 fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title);        /* Centered */
          }

          /* print headers */
***************
*** 557,563 ****
              int            line_count;

              if (opt_border == 2)
!                 _print_horizontal_line(col_count, widths, opt_border, fout);

              for (i = 0; i < col_count; i++)
                  pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i],
heights[i]);
--- 654,660 ----
              int            line_count;

              if (opt_border == 2)
!                 _print_horizontal_line(col_count, width_wrap, opt_border, fout);

              for (i = 0; i < col_count; i++)
                  pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i],
heights[i]);
***************
*** 580,586 ****

                      if (!complete[i])
                      {
!                         nbspace = widths[i] - this_line->width;

                          /* centered */
                          fprintf(fout, "%-*s%s%-*s",
--- 677,683 ----

                      if (!complete[i])
                      {
!                         nbspace = width_wrap[i] - this_line->width;

                          /* centered */
                          fprintf(fout, "%-*s%s%-*s",
***************
*** 593,599 ****
                          }
                      }
                      else
!                         fprintf(fout, "%*s", widths[i], "");
                      if (i < col_count - 1)
                      {
                          if (opt_border == 0)
--- 690,696 ----
                          }
                      }
                      else
!                         fprintf(fout, "%*s", width_wrap[i], "");
                      if (i < col_count - 1)
                      {
                          if (opt_border == 0)
***************
*** 611,711 ****
                  fputc('\n', fout);
              }

!             _print_horizontal_line(col_count, widths, opt_border, fout);
          }
      }

      /* print cells */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         int            j;
!         int            cols_todo = col_count;
!         int            line_count; /* Number of lines output so far in row */

          if (cancel_pressed)
              break;

          for (j = 0; j < col_count; j++)
              pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);

!         line_count = 0;
          memset(complete, 0, col_count * sizeof(int));
!         while (cols_todo)
          {
!             /* beginning of line */
!             if (opt_border == 2)
!                 fputs("| ", fout);
!             else if (opt_border == 1)
!                 fputc(' ', fout);
!
!             for (j = 0; j < col_count; j++)
              {
!                 struct lineptr *this_line = col_lineptrs[j] + line_count;
!                 bool        finalspaces = (opt_border == 2 || j != col_count - 1);

!                 if (complete[j])    /* Just print spaces... */
!                 {
!                     if (finalspaces)
!                         fprintf(fout, "%*s", widths[j], "");
!                 }
!                 else
                  {
!                     /* content */
!                     if (opt_align[j] == 'r')
                      {
!                         if (opt_numeric_locale)
                          {
!                             /*
!                              * Assumption: This code used only on strings
!                              * without multibyte characters, otherwise
!                              * this_line->width < strlen(this_ptr) and we get
!                              * an overflow
!                              */
!                             char       *my_cell = format_numeric_locale((char *) this_line->ptr);
!
!                             fprintf(fout, "%*s%s",
!                                     (int) (widths[i % col_count] - strlen(my_cell)), "",
!                                     my_cell);
!                             free(my_cell);
                          }
                          else
!                             fprintf(fout, "%*s%s",
!                                     widths[j] - this_line->width, "",
!                                     this_line->ptr);
                      }
!                     else
!                         fprintf(fout, "%-s%*s", this_line->ptr,
!                         finalspaces ? (widths[j] - this_line->width) : 0, "");
!                     /* If at the right height, done this col */
!                     if (line_count == heights[j] - 1 || !this_line[1].ptr)
                      {
!                         complete[j] = 1;
!                         cols_todo--;
                      }
                  }

!                 /* divider */
!                 if ((j + 1) % col_count)
                  {
!                     if (opt_border == 0)
!                         fputc(' ', fout);
!                     else if (line_count == 0)
!                         fputs(" | ", fout);
!                     else
!                         fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':');
                  }
              }
-             if (opt_border == 2)
-                 fputs(" |", fout);
-             fputc('\n', fout);
-             line_count++;
          }
      }

      if (opt->stop_table)
      {
          if (opt_border == 2 && !cancel_pressed)
!             _print_horizontal_line(col_count, widths, opt_border, fout);

          /* print footers */
          if (footers && !opt_tuples_only && !cancel_pressed)
--- 708,855 ----
                  fputc('\n', fout);
              }

!             _print_horizontal_line(col_count, width_wrap, opt_border, fout);
          }
      }

      /* print cells */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         bool        line_todo,
!                     cols_todo;
!         int            line_count;

          if (cancel_pressed)
              break;

+         /*
+          * Format each cell.  Format again, it is a numeric formatting locale
+          * (e.g. 123,456 vs. 123456)
+          */
          for (j = 0; j < col_count; j++)
+         {
              pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);
+             if (opt_numeric_locale && opt_align[j % col_count] == 'r')
+             {
+                 char       *my_cell;

!                 my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr);
!                 strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large
!                                                                  * enough... now */
!                 free(my_cell);
!             }
!         }
!
!         /* Print rows to console */
          memset(complete, 0, col_count * sizeof(int));
!         line_count = 0;
!         line_todo = true;
!         while (line_todo)
          {
!             cols_todo = true;
!             while (cols_todo)
              {
!                 char    border_cell = '*';

!                 cols_todo = false;
!
!                 /* left border */
!                 if (opt_border == 2)
!                     fputs("| ", fout);
!                 else if (opt_border == 1)
!                     fputc(' ', fout);
!
!                 /* for each column */
!                 for (j = 0; j < col_count; j++)
                  {
!                     struct lineptr *this_line = &col_lineptrs[j][line_count];
!
!                     if (heights[j] <= line_count)        /* Blank column content */
!                     {
!                         fprintf(fout, "%*s", width_wrap[j], "");
!                         border_cell = '|';
!                     }
!                     else if (opt_align[j] == 'r')        /* Right aligned cell */
                      {
!                         int        strlen_remaining = strlen((char *) this_line->ptr + complete[j]);
!
!                         if (strlen_remaining > width_wrap[j])
                          {
!                             fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]);
!                             complete[j] += width_wrap[j];        /* We've done THIS much */
!                             cols_todo = true;    /* And there is more to do... */
!                             border_cell = ':';
                          }
                          else
!                         {
!                             fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, "");
!                             fprintf(fout, "%-s", this_line->ptr + complete[j]);
!                             complete[j] += strlen_remaining;
!                             border_cell = '|';
!                         }
                      }
!                     else    /* Left aligned cell */
                      {
!                         int        strlen_remaining = strlen((char *) this_line->ptr + complete[j]);
!
!                         if (strlen_remaining > width_wrap[j])
!                         {
!                             fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]);
!                             complete[j] += width_wrap[j];        /* We've done THIS much */
!                             cols_todo = true;    /* And there is more to do... */
!                             border_cell = ':';
!                         }
!                         else
!                         {
!                             fprintf(fout, "%-s", this_line->ptr + complete[j]);
!                             fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, "");
!                             complete[j] += strlen_remaining;
!                             border_cell = '|';
!                         }
!                     }
!
!                     /* print a divider, middle of columns only */
!                     if ((j + 1) % col_count)
!                     {
!                         if (opt_border == 0)
!                             fputc(' ', fout);
!                         else if (line_count == 0)
!                             fprintf(fout, " %c ", border_cell);
!                         else
!                             fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':');
                      }
                  }

!                 /* end of row border */
!                 if (opt_border == 2)
!                     fprintf(fout, " %c", border_cell);
!                 fputc('\n', fout);
!             }
!
!             /*
!              * Check if any columns have line continuations due to \n in the
!              * cell.
!              */
!             line_count++;
!             line_todo = false;
!             for (j = 0; j < col_count; j++)
!             {
!                 if (line_count < heights[j])
                  {
!                     if (col_lineptrs[j][line_count].ptr)
!                     {
!                         line_todo = true;
!                         complete[j] = 0;
!                     }
                  }
              }
          }
      }

      if (opt->stop_table)
      {
          if (opt_border == 2 && !cancel_pressed)
!             _print_horizontal_line(col_count, width_wrap, opt_border, fout);

          /* print footers */
          if (footers && !opt_tuples_only && !cancel_pressed)
***************
*** 722,728 ****
      }

      /* clean up */
!     free(widths);
      free(heights);
      free(col_lineptrs);
      free(format_space);
--- 866,875 ----
      }

      /* clean up */
!     free(width_header);
!     free(width_average);
!     free(width_max);
!     free(width_wrap);
      free(heights);
      free(col_lineptrs);
      free(format_space);
***************
*** 754,760 ****
                  dheight = 1,
                  hformatsize = 0,
                  dformatsize = 0;
-     int            tmp = 0;
      char       *divider;
      unsigned int cell_count = 0;
      struct lineptr *hlineptr,
--- 901,906 ----
***************
*** 779,790 ****
      /* Find the maximum dimensions for the headers */
      for (i = 0; i < col_count; i++)
      {
!         int            height,
                      fs;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs);
!         if (tmp > hwidth)
!             hwidth = tmp;
          if (height > hheight)
              hheight = height;
          if (fs > hformatsize)
--- 925,937 ----
      /* Find the maximum dimensions for the headers */
      for (i = 0; i < col_count; i++)
      {
!         int            width,
!                     height,
                      fs;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs);
!         if (width > hwidth)
!             hwidth = width;
          if (height > hheight)
              hheight = height;
          if (fs > hformatsize)
***************
*** 799,805 ****
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
          int            numeric_locale_len;
!         int            height,
                      fs;

          if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
--- 946,953 ----
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
          int            numeric_locale_len;
!         int            width,
!                     height,
                      fs;

          if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
***************
*** 807,816 ****
          else
              numeric_locale_len = 0;

!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &fs);
!         tmp += numeric_locale_len;
!         if (tmp > dwidth)
!             dwidth = tmp;
          if (height > dheight)
              dheight = height;
          if (fs > dformatsize)
--- 955,964 ----
          else
              numeric_locale_len = 0;

!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs);
!         width += numeric_locale_len;
!         if (width > dwidth)
!             dwidth = width;
          if (height > dheight)
              dheight = height;
          if (fs > dformatsize)
***************
*** 1879,1884 ****
--- 2027,2033 ----
                                       opt, output);
              break;
          case PRINT_ALIGNED:
+         case PRINT_WRAP:
              if (opt->expanded)
                  print_aligned_vertical(title, headers, cells, footers, align,
                                         opt, output);
Index: src/bin/psql/print.h
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v
retrieving revision 1.35
diff -c -c -r1.35 print.h
*** src/bin/psql/print.h    1 Jan 2008 19:45:56 -0000    1.35
--- src/bin/psql/print.h    17 Apr 2008 02:45:38 -0000
***************
*** 21,26 ****
--- 21,27 ----
      PRINT_NOTHING = 0,            /* to make sure someone initializes this */
      PRINT_UNALIGNED,
      PRINT_ALIGNED,
+     PRINT_WRAP,
      PRINT_HTML,
      PRINT_LATEX,
      PRINT_TROFF_MS

Re: Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:

Bruce Momjian wrote:
> I spent time reviewing your patch --- quite impressive. I have attached
> and updated version with mostly stylistic changes.
>
> In testing I found the regression tests were failing because of a divide
> by zero error (fixed), and a missing case where the column delimiter has
> to be ":".  In fact I now see all your line continuation cases using ":"
> rather than "!".  It actually looks better --- "!" was too close to "|"
> to be easily recognized.  (Did you update your patch to use ":".  I
> didn't see "!" in your patch.)
Nice!  I'll merge with my current version.  As you note I changed to ":".

I also found that for hugely wide output it was better to give up (do
nothing), rather than mangle the output in a futile attempt to squash it
to the window width.  So there is one more clause in the wrapping if.

I have tested on several unix flavors, but not on Windows or cygwin.

                            -Bryce

Re: Proposed patch - psql wraps at window width

From
Alvaro Herrera
Date:
Bruce Momjian wrote:

> In testing I found the regression tests were failing because of a divide
> by zero error (fixed), and a missing case where the column delimiter has
> to be ":".  In fact I now see all your line continuation cases using ":"
> rather than "!".  It actually looks better --- "!" was too close to "|"
> to be easily recognized.  (Did you update your patch to use ":".  I
> didn't see "!" in your patch.)

I think we should use a different separator when there is an actual
newline in the data.  Currently we have a : there, so using a : here is
probably not the best idea.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Re: Proposed patch - psql wraps at window width

From
Peter Eisentraut
Date:
Bruce Momjian wrote:
> I checked the use of COLUMNS and it seems bash updates the environment
> variable when a window is resized.  I added ioctl(TIOCGWINSZ) if COLUMNS
> isn't set.  We already had a call in print.c for detecting the
> number of rows on the screen to determine if the pager should
> be used.  Seems COLUMNS should take precedence over ioctl(), right?

Considering that the code to determine the row count is undisputed so far, the
column count detection should work the same.  That is, we might not need to
look at COLUMNS at all.  Unless there is a use case for overriding the column
count (instead of just turning off the wrapping).

Re: Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
Bryce Nesbitt wrote:
>
>
> Bruce Momjian wrote:
> > I spent time reviewing your patch --- quite impressive. I have attached
> > and updated version with mostly stylistic changes.
> >
> > In testing I found the regression tests were failing because of a divide
> > by zero error (fixed), and a missing case where the column delimiter has
> > to be ":".  In fact I now see all your line continuation cases using ":"
> > rather than "!".  It actually looks better --- "!" was too close to "|"
> > to be easily recognized.  (Did you update your patch to use ":".  I
> > didn't see "!" in your patch.)
> Nice!  I'll merge with my current version.  As you note I changed to ":".

Good, I thought so.

> I also found that for hugely wide output it was better to give up (do
> nothing), rather than mangle the output in a futile attempt to squash it
> to the window width.  So there is one more clause in the wrapping if.

Was it because of performance?  I have a way to fix that by decrementing
by more than one to shrink a column?  I am attaching a new patch with my
improved loop.  It remembers the previous maximum ratio.

> I have tested on several unix flavors, but not on Windows or cygwin.

I don't think you need to do that to get it applied --- there is nothing
windows-specific in your code.

Is this ready to be applied?  Do you want to send a final update or are
you still testing?

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +

Index: doc/src/sgml/ref/psql-ref.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v
retrieving revision 1.199
diff -c -c -r1.199 psql-ref.sgml
*** doc/src/sgml/ref/psql-ref.sgml    30 Mar 2008 18:10:20 -0000    1.199
--- doc/src/sgml/ref/psql-ref.sgml    17 Apr 2008 14:05:39 -0000
***************
*** 1513,1519 ****
            <listitem>
            <para>
            Sets the output format to one of <literal>unaligned</literal>,
!           <literal>aligned</literal>, <literal>html</literal>,
            <literal>latex</literal>, or <literal>troff-ms</literal>.
            Unique abbreviations are allowed.  (That would mean one letter
            is enough.)
--- 1513,1520 ----
            <listitem>
            <para>
            Sets the output format to one of <literal>unaligned</literal>,
!           <literal>aligned</literal>, <literal>wrapped</literal>,
!           <literal>html</literal>,
            <literal>latex</literal>, or <literal>troff-ms</literal>.
            Unique abbreviations are allowed.  (That would mean one letter
            is enough.)
***************
*** 1525,1531 ****
            is intended to create output that might be intended to be read
            in by other programs (tab-separated, comma-separated).
            <quote>Aligned</quote> mode is the standard, human-readable,
!           nicely formatted text output that is default. The
            <quote><acronym>HTML</acronym></quote> and
            <quote>LaTeX</quote> modes put out tables that are intended to
            be included in documents using the respective mark-up
--- 1526,1535 ----
            is intended to create output that might be intended to be read
            in by other programs (tab-separated, comma-separated).
            <quote>Aligned</quote> mode is the standard, human-readable,
!           nicely formatted text output that is default.
!           <quote>Wrapped</quote> is like <literal>aligned</> but wraps
!           the output to fit the screen's width, based on the environment
!           variable <envar>COLUMNS</> or the autodetected width. The
            <quote><acronym>HTML</acronym></quote> and
            <quote>LaTeX</quote> modes put out tables that are intended to
            be included in documents using the respective mark-up
***************
*** 2708,2713 ****
--- 2712,2730 ----

    <variablelist>
     <varlistentry>
+     <term><envar>COLUMNS</envar></term>
+
+     <listitem>
+      <para>
+       The character width to wrap output in <literal>wrapped</> format
+       mode.  Many shells automatically update <envar>COLUMNS</> when
+       a window is resized.  If not set the screen width is automatically
+       detected, if possible.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
      <term><envar>PAGER</envar></term>

      <listitem>
Index: src/bin/psql/command.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v
retrieving revision 1.186
diff -c -c -r1.186 command.c
*** src/bin/psql/command.c    1 Jan 2008 19:45:55 -0000    1.186
--- src/bin/psql/command.c    17 Apr 2008 14:05:39 -0000
***************
*** 1526,1531 ****
--- 1526,1534 ----
          case PRINT_ALIGNED:
              return "aligned";
              break;
+         case PRINT_WRAP:
+             return "wrapped";
+             break;
          case PRINT_HTML:
              return "html";
              break;
***************
*** 1559,1564 ****
--- 1562,1569 ----
              popt->topt.format = PRINT_UNALIGNED;
          else if (pg_strncasecmp("aligned", value, vallen) == 0)
              popt->topt.format = PRINT_ALIGNED;
+         else if (pg_strncasecmp("wrapped", value, vallen) == 0)
+             popt->topt.format = PRINT_WRAP;
          else if (pg_strncasecmp("html", value, vallen) == 0)
              popt->topt.format = PRINT_HTML;
          else if (pg_strncasecmp("latex", value, vallen) == 0)
***************
*** 1567,1573 ****
              popt->topt.format = PRINT_TROFF_MS;
          else
          {
!             psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n");
              return false;
          }

--- 1572,1578 ----
              popt->topt.format = PRINT_TROFF_MS;
          else
          {
!             psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
              return false;
          }

Index: src/bin/psql/mbprint.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/mbprint.c,v
retrieving revision 1.30
diff -c -c -r1.30 mbprint.c
*** src/bin/psql/mbprint.c    16 Apr 2008 18:18:00 -0000    1.30
--- src/bin/psql/mbprint.c    17 Apr 2008 14:05:40 -0000
***************
*** 279,284 ****
--- 279,288 ----
      return width;
  }

+ /*
+  *  Filter out unprintable characters, companion to wcs_size.
+  *  Break input into lines (based on \n or \r).
+  */
  void
  pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
               struct lineptr * lines, int count)
***************
*** 353,364 ****
          }
          len -= chlen;
      }
!     *ptr++ = '\0';
      lines->width = linewidth;
!     lines++;
!     count--;
!     if (count > 0)
          lines->ptr = NULL;
  }

  unsigned char *
--- 357,373 ----
          }
          len -= chlen;
      }
!     *ptr++ = '\0';            /* Terminate formatted string */
!
      lines->width = linewidth;
!
!     /* Fill remaining array slots with nulls */
!     while (--count)
!     {
!         lines++;
          lines->ptr = NULL;
+         lines->width = 0;
+     }
  }

  unsigned char *
Index: src/bin/psql/print.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v
retrieving revision 1.97
diff -c -c -r1.97 print.c
*** src/bin/psql/print.c    27 Mar 2008 03:57:34 -0000    1.97
--- src/bin/psql/print.c    17 Apr 2008 14:05:40 -0000
***************
*** 43,48 ****
--- 43,52 ----
  static char *grouping;
  static char *thousands_sep;

+ #define COL_RATIO(col_num) \
+             ((double) width_wrap[col_num] / width_average[col_num] + \
+             width_max[col_num] * 0.01)        /* Penalize wide columns */
+
  static void *
  pg_local_malloc(size_t size)
  {
***************
*** 396,401 ****
--- 400,408 ----
  }


+ /*
+  *    Prety pretty boxes around cells.
+  */
  static void
  print_aligned_text(const char *title, const char *const * headers,
                     const char *const * cells, const char *const * footers,
***************
*** 404,429 ****
  {
      bool        opt_tuples_only = opt->tuples_only;
      bool        opt_numeric_locale = opt->numericLocale;
-     unsigned short int opt_border = opt->border;
      int            encoding = opt->encoding;
!     unsigned int col_count = 0;
!     unsigned int cell_count = 0;
!     unsigned int i;
!     int            tmp;
!     unsigned int *widths,
!                 total_w;
!     unsigned int *heights;
!     unsigned int *format_space;
      unsigned char **format_buf;

      const char *const * ptr;

!     struct lineptr **col_lineptrs;        /* pointers to line pointer for each
!                                          * column */
      struct lineptr *lineptr_list;        /* complete list of linepointers */

      int           *complete;        /* Array remembering which columns have
                                   * completed output */

      if (cancel_pressed)
          return;
--- 411,441 ----
  {
      bool        opt_tuples_only = opt->tuples_only;
      bool        opt_numeric_locale = opt->numericLocale;
      int            encoding = opt->encoding;
!     unsigned short int opt_border = opt->border;
!
!     unsigned int col_count = 0, cell_count = 0;
!
!     unsigned int i,
!                 j;
!
!     unsigned int *width_header,
!                *width_max,
!                *width_wrap,
!                *width_average;
!     unsigned int *heights,
!                *format_space;
      unsigned char **format_buf;
+     unsigned int width_total;

      const char *const * ptr;

!     struct lineptr **col_lineptrs;        /* pointers to line pointer per column */
      struct lineptr *lineptr_list;        /* complete list of linepointers */

      int           *complete;        /* Array remembering which columns have
                                   * completed output */
+     int            tcolumns = 0;    /* Width of interactive console */

      if (cancel_pressed)
          return;
***************
*** 437,443 ****

      if (col_count > 0)
      {
!         widths = pg_local_calloc(col_count, sizeof(*widths));
          heights = pg_local_calloc(col_count, sizeof(*heights));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
          format_space = pg_local_calloc(col_count, sizeof(*format_space));
--- 449,458 ----

      if (col_count > 0)
      {
!         width_header = pg_local_calloc(col_count, sizeof(*width_header));
!         width_average = pg_local_calloc(col_count, sizeof(*width_average));
!         width_max = pg_local_calloc(col_count, sizeof(*width_max));
!         width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap));
          heights = pg_local_calloc(col_count, sizeof(*heights));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
          format_space = pg_local_calloc(col_count, sizeof(*format_space));
***************
*** 446,452 ****
      }
      else
      {
!         widths = NULL;
          heights = NULL;
          col_lineptrs = NULL;
          format_space = NULL;
--- 461,470 ----
      }
      else
      {
!         width_header = NULL;
!         width_average = NULL;
!         width_max = NULL;
!         width_wrap = NULL;
          heights = NULL;
          col_lineptrs = NULL;
          format_space = NULL;
***************
*** 454,533 ****
          complete = NULL;
      }

!     /* count cells (rows * cols) */
!     for (ptr = cells; *ptr; ptr++)
!         cell_count++;
!
!     /* calc column widths */
      for (i = 0; i < col_count; i++)
      {
          /* Get width & height */
!         int            height,
                      space;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space);
!         if (tmp > widths[i])
!             widths[i] = tmp;
          if (height > heights[i])
              heights[i] = height;
          if (space > format_space[i])
              format_space[i] = space;
      }

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

-         if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
-             numeric_locale_len = additional_numeric_locale_len(*ptr);
-         else
-             numeric_locale_len = 0;
-
          /* Get width, ignore height */
!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space);
!         tmp += numeric_locale_len;
!         if (tmp > widths[i % col_count])
!             widths[i % col_count] = tmp;
          if (height > heights[i % col_count])
              heights[i % col_count] = height;
          if (space > format_space[i % col_count])
              format_space[i % col_count] = space;
      }

      if (opt_border == 0)
!         total_w = col_count - 1;
      else if (opt_border == 1)
!         total_w = col_count * 3 - 1;
      else
!         total_w = col_count * 3 + 1;

      for (i = 0; i < col_count; i++)
!         total_w += widths[i];

      /*
!      * At this point: widths contains the max width of each column heights
!      * contains the max height of a cell of each column format_space contains
!      * maximum space required to store formatted string so we prepare the
!      * formatting structures
       */
      if (col_count > 0)
      {
!         int            heights_total = 0;
          struct lineptr *lineptr;

          for (i = 0; i < col_count; i++)
!             heights_total += heights[i];

!         lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list));

          for (i = 0; i < col_count; i++)
          {
              col_lineptrs[i] = lineptr;
              lineptr += heights[i];

!             format_buf[i] = pg_local_malloc(format_space[i]);

              col_lineptrs[i]->ptr = format_buf[i];
          }
--- 472,564 ----
          complete = NULL;
      }

!     /* scan all column headers, find maximum width */
      for (i = 0; i < col_count; i++)
      {
          /* Get width & height */
!         int            width,
!                     height,
                      space;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &space);
!         if (width > width_max[i])
!             width_max[i] = width;
          if (height > heights[i])
              heights[i] = height;
          if (space > format_space[i])
              format_space[i] = space;
+
+         width_header[i] = width;
      }

!     /* scan all rows, find maximum width, compute cell_count */
!     for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++)
      {
!         int            width,
!                     height,
                      space;

          /* Get width, ignore height */
!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &space);
!         if (opt_numeric_locale && opt_align[i % col_count] == 'r')
!         {
!             width += additional_numeric_locale_len(*ptr);
!             space += additional_numeric_locale_len(*ptr);
!         }
!
!         if (width > width_max[i % col_count])
!             width_max[i % col_count] = width;
          if (height > heights[i % col_count])
              heights[i % col_count] = height;
          if (space > format_space[i % col_count])
              format_space[i % col_count] = space;
+
+         width_average[i % col_count] += width;
      }

+     /* If we have rows, compute average */
+     if (col_count != 0 && cell_count != 0)
+     {
+         int rows = cell_count / col_count;
+
+         for (i = 0; i < col_count; i++)
+             width_average[i % col_count] /= rows;
+     }
+
+     /* adjust the total display width based on border style */
      if (opt_border == 0)
!         width_total = col_count - 1;
      else if (opt_border == 1)
!         width_total = col_count * 3 - 1;
      else
!         width_total = col_count * 3 + 1;

      for (i = 0; i < col_count; i++)
!         width_total += width_max[i];

      /*
!      * At this point: width_max[] contains the max width of each column,
!      * heights[] contains the max number of lines in each column,
!      * format_space[] contains the maximum storage space for formatting
!      * strings, width_total contains the giant width sum.  Now we allocate
!      * some memory...
       */
      if (col_count > 0)
      {
!         int            height_total = 0;
          struct lineptr *lineptr;

          for (i = 0; i < col_count; i++)
!             height_total += heights[i];

!         lineptr = lineptr_list = pg_local_calloc(height_total, sizeof(*lineptr_list));

          for (i = 0; i < col_count; i++)
          {
              col_lineptrs[i] = lineptr;
              lineptr += heights[i];

!             format_buf[i] = pg_local_malloc(format_space[i] + 1);

              col_lineptrs[i]->ptr = format_buf[i];
          }
***************
*** 535,553 ****
      else
          lineptr_list = NULL;

      if (opt->start_table)
      {
          /* print title */
          if (title && !opt_tuples_only)
          {
!             /* Get width & height */
!             int            height;

!             pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL);
!             if (tmp >= total_w)
!                 fprintf(fout, "%s\n", title);
              else
!                 fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title);
          }

          /* print headers */
--- 566,652 ----
      else
          lineptr_list = NULL;

+
+     /* Default word wrap to the full width, i.e. no word wrap */
+     for (i = 0; i < col_count; i++)
+         width_wrap[i] = width_max[i];
+
+     /*
+      * Optional optimized word wrap. Shrink columns with a high max/avg ratio.
+      * Slighly bias against wider columns (increases chance a narrow column
+      * will fit in its cell)
+      */
+     if (opt->format == PRINT_WRAP)
+     {
+         /* If we can get the terminal width */
+         char       *env = getenv("COLUMNS");
+
+         if (env != NULL)
+             tcolumns = atoi(env);
+ #ifdef TIOCGWINSZ
+         else
+         {
+             struct winsize screen_size;
+
+             if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
+                 tcolumns = screen_size.ws_col;
+         }
+ #endif
+
+         if (tcolumns > 0)
+         {
+             /* Shink high ratio columns */
+             while (width_total > tcolumns)
+             {
+                 double        max_ratio = 0, prev_max_ratio = 0;
+                 double        curr_ratio = 0;
+                 int            worst_col = -1;
+
+                 /*
+                  *    Find column that has the highest ratio of its maximum
+                  *    width compared to its average width.  This tells us which
+                  *    column will produce the fewest wrapped values if shortened.
+                  *    width_wrap starts as equal to width_max.
+                  */
+                 for (i = 0; i < col_count; i++)
+                     if (width_average[i] && width_wrap[i] > width_header[i])
+                     {
+                         if (COL_RATIO(i) > max_ratio)
+                         {
+                             prev_max_ratio = max_ratio;
+                             max_ratio = curr_ratio;
+                             worst_col = i;
+                         }
+                     }
+
+                 /* Exit loop if we can't squeeze any more. */
+                 if (worst_col < 0)
+                     break;
+
+                 /* Squeeze the worst column.  Lather, rinse, repeat */
+                 do
+                 {
+                     width_wrap[worst_col]--;
+                     width_total--;
+                 } while (width_total > tcolumns && COL_RATIO(i) >= prev_max_ratio);
+             }
+         }
+     }
+
+     /* time to output */
      if (opt->start_table)
      {
          /* print title */
          if (title && !opt_tuples_only)
          {
!             int            width,
!                         height;

!             pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL);
!             if (width >= width_total)
!                 fprintf(fout, "%s\n", title);    /* Aligned */
              else
!                 fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title);        /* Centered */
          }

          /* print headers */
***************
*** 557,563 ****
              int            line_count;

              if (opt_border == 2)
!                 _print_horizontal_line(col_count, widths, opt_border, fout);

              for (i = 0; i < col_count; i++)
                  pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i],
heights[i]);
--- 656,662 ----
              int            line_count;

              if (opt_border == 2)
!                 _print_horizontal_line(col_count, width_wrap, opt_border, fout);

              for (i = 0; i < col_count; i++)
                  pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i],
heights[i]);
***************
*** 580,586 ****

                      if (!complete[i])
                      {
!                         nbspace = widths[i] - this_line->width;

                          /* centered */
                          fprintf(fout, "%-*s%s%-*s",
--- 679,685 ----

                      if (!complete[i])
                      {
!                         nbspace = width_wrap[i] - this_line->width;

                          /* centered */
                          fprintf(fout, "%-*s%s%-*s",
***************
*** 593,599 ****
                          }
                      }
                      else
!                         fprintf(fout, "%*s", widths[i], "");
                      if (i < col_count - 1)
                      {
                          if (opt_border == 0)
--- 692,698 ----
                          }
                      }
                      else
!                         fprintf(fout, "%*s", width_wrap[i], "");
                      if (i < col_count - 1)
                      {
                          if (opt_border == 0)
***************
*** 611,711 ****
                  fputc('\n', fout);
              }

!             _print_horizontal_line(col_count, widths, opt_border, fout);
          }
      }

      /* print cells */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         int            j;
!         int            cols_todo = col_count;
!         int            line_count; /* Number of lines output so far in row */

          if (cancel_pressed)
              break;

          for (j = 0; j < col_count; j++)
              pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);

!         line_count = 0;
          memset(complete, 0, col_count * sizeof(int));
!         while (cols_todo)
          {
!             /* beginning of line */
!             if (opt_border == 2)
!                 fputs("| ", fout);
!             else if (opt_border == 1)
!                 fputc(' ', fout);
!
!             for (j = 0; j < col_count; j++)
              {
!                 struct lineptr *this_line = col_lineptrs[j] + line_count;
!                 bool        finalspaces = (opt_border == 2 || j != col_count - 1);

!                 if (complete[j])    /* Just print spaces... */
!                 {
!                     if (finalspaces)
!                         fprintf(fout, "%*s", widths[j], "");
!                 }
!                 else
                  {
!                     /* content */
!                     if (opt_align[j] == 'r')
                      {
!                         if (opt_numeric_locale)
                          {
!                             /*
!                              * Assumption: This code used only on strings
!                              * without multibyte characters, otherwise
!                              * this_line->width < strlen(this_ptr) and we get
!                              * an overflow
!                              */
!                             char       *my_cell = format_numeric_locale((char *) this_line->ptr);
!
!                             fprintf(fout, "%*s%s",
!                                     (int) (widths[i % col_count] - strlen(my_cell)), "",
!                                     my_cell);
!                             free(my_cell);
                          }
                          else
!                             fprintf(fout, "%*s%s",
!                                     widths[j] - this_line->width, "",
!                                     this_line->ptr);
                      }
!                     else
!                         fprintf(fout, "%-s%*s", this_line->ptr,
!                         finalspaces ? (widths[j] - this_line->width) : 0, "");
!                     /* If at the right height, done this col */
!                     if (line_count == heights[j] - 1 || !this_line[1].ptr)
                      {
!                         complete[j] = 1;
!                         cols_todo--;
                      }
                  }

!                 /* divider */
!                 if ((j + 1) % col_count)
                  {
!                     if (opt_border == 0)
!                         fputc(' ', fout);
!                     else if (line_count == 0)
!                         fputs(" | ", fout);
!                     else
!                         fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':');
                  }
              }
-             if (opt_border == 2)
-                 fputs(" |", fout);
-             fputc('\n', fout);
-             line_count++;
          }
      }

      if (opt->stop_table)
      {
          if (opt_border == 2 && !cancel_pressed)
!             _print_horizontal_line(col_count, widths, opt_border, fout);

          /* print footers */
          if (footers && !opt_tuples_only && !cancel_pressed)
--- 710,857 ----
                  fputc('\n', fout);
              }

!             _print_horizontal_line(col_count, width_wrap, opt_border, fout);
          }
      }

      /* print cells */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         bool        line_todo,
!                     cols_todo;
!         int            line_count;

          if (cancel_pressed)
              break;

+         /*
+          * Format each cell.  Format again, it is a numeric formatting locale
+          * (e.g. 123,456 vs. 123456)
+          */
          for (j = 0; j < col_count; j++)
+         {
              pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);
+             if (opt_numeric_locale && opt_align[j % col_count] == 'r')
+             {
+                 char       *my_cell;

!                 my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr);
!                 strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large
!                                                                  * enough... now */
!                 free(my_cell);
!             }
!         }
!
!         /* Print rows to console */
          memset(complete, 0, col_count * sizeof(int));
!         line_count = 0;
!         line_todo = true;
!         while (line_todo)
          {
!             cols_todo = true;
!             while (cols_todo)
              {
!                 char    border_cell = '*';

!                 cols_todo = false;
!
!                 /* left border */
!                 if (opt_border == 2)
!                     fputs("| ", fout);
!                 else if (opt_border == 1)
!                     fputc(' ', fout);
!
!                 /* for each column */
!                 for (j = 0; j < col_count; j++)
                  {
!                     struct lineptr *this_line = &col_lineptrs[j][line_count];
!
!                     if (heights[j] <= line_count)        /* Blank column content */
!                     {
!                         fprintf(fout, "%*s", width_wrap[j], "");
!                         border_cell = '|';
!                     }
!                     else if (opt_align[j] == 'r')        /* Right aligned cell */
                      {
!                         int        strlen_remaining = strlen((char *) this_line->ptr + complete[j]);
!
!                         if (strlen_remaining > width_wrap[j])
                          {
!                             fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]);
!                             complete[j] += width_wrap[j];        /* We've done THIS much */
!                             cols_todo = true;    /* And there is more to do... */
!                             border_cell = ':';
                          }
                          else
!                         {
!                             fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, "");
!                             fprintf(fout, "%-s", this_line->ptr + complete[j]);
!                             complete[j] += strlen_remaining;
!                             border_cell = '|';
!                         }
                      }
!                     else    /* Left aligned cell */
!                     {
!                         int        strlen_remaining = strlen((char *) this_line->ptr + complete[j]);
!
!                         if (strlen_remaining > width_wrap[j])
!                         {
!                             fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]);
!                             complete[j] += width_wrap[j];        /* We've done THIS much */
!                             cols_todo = true;    /* And there is more to do... */
!                             border_cell = ':';
!                         }
!                         else
!                         {
!                             fprintf(fout, "%-s", this_line->ptr + complete[j]);
!                             fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, "");
!                             complete[j] += strlen_remaining;
!                             border_cell = '|';
!                         }
!                     }
!
!                     /* print a divider, middle of columns only */
!                     if ((j + 1) % col_count)
                      {
!                         if (opt_border == 0)
!                             fputc(' ', fout);
!                         else if (line_count == 0)
!                             fprintf(fout, " %c ", border_cell);
!                         else
!                             fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':');
                      }
                  }

!                 /* end of row border */
!                 if (opt_border == 2)
!                     fprintf(fout, " %c", border_cell);
!                 fputc('\n', fout);
!             }
!
!             /*
!              * Check if any columns have line continuations due to \n in the
!              * cell.
!              */
!             line_count++;
!             line_todo = false;
!             for (j = 0; j < col_count; j++)
!             {
!                 if (line_count < heights[j])
                  {
!                     if (col_lineptrs[j][line_count].ptr)
!                     {
!                         line_todo = true;
!                         complete[j] = 0;
!                     }
                  }
              }
          }
      }

      if (opt->stop_table)
      {
          if (opt_border == 2 && !cancel_pressed)
!             _print_horizontal_line(col_count, width_wrap, opt_border, fout);

          /* print footers */
          if (footers && !opt_tuples_only && !cancel_pressed)
***************
*** 722,728 ****
      }

      /* clean up */
!     free(widths);
      free(heights);
      free(col_lineptrs);
      free(format_space);
--- 868,877 ----
      }

      /* clean up */
!     free(width_header);
!     free(width_average);
!     free(width_max);
!     free(width_wrap);
      free(heights);
      free(col_lineptrs);
      free(format_space);
***************
*** 754,760 ****
                  dheight = 1,
                  hformatsize = 0,
                  dformatsize = 0;
-     int            tmp = 0;
      char       *divider;
      unsigned int cell_count = 0;
      struct lineptr *hlineptr,
--- 903,908 ----
***************
*** 779,790 ****
      /* Find the maximum dimensions for the headers */
      for (i = 0; i < col_count; i++)
      {
!         int            height,
                      fs;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs);
!         if (tmp > hwidth)
!             hwidth = tmp;
          if (height > hheight)
              hheight = height;
          if (fs > hformatsize)
--- 927,939 ----
      /* Find the maximum dimensions for the headers */
      for (i = 0; i < col_count; i++)
      {
!         int            width,
!                     height,
                      fs;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs);
!         if (width > hwidth)
!             hwidth = width;
          if (height > hheight)
              hheight = height;
          if (fs > hformatsize)
***************
*** 799,805 ****
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
          int            numeric_locale_len;
!         int            height,
                      fs;

          if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
--- 948,955 ----
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
          int            numeric_locale_len;
!         int            width,
!                     height,
                      fs;

          if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
***************
*** 807,816 ****
          else
              numeric_locale_len = 0;

!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &fs);
!         tmp += numeric_locale_len;
!         if (tmp > dwidth)
!             dwidth = tmp;
          if (height > dheight)
              dheight = height;
          if (fs > dformatsize)
--- 957,966 ----
          else
              numeric_locale_len = 0;

!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs);
!         width += numeric_locale_len;
!         if (width > dwidth)
!             dwidth = width;
          if (height > dheight)
              dheight = height;
          if (fs > dformatsize)
***************
*** 1879,1884 ****
--- 2029,2035 ----
                                       opt, output);
              break;
          case PRINT_ALIGNED:
+         case PRINT_WRAP:
              if (opt->expanded)
                  print_aligned_vertical(title, headers, cells, footers, align,
                                         opt, output);
Index: src/bin/psql/print.h
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v
retrieving revision 1.35
diff -c -c -r1.35 print.h
*** src/bin/psql/print.h    1 Jan 2008 19:45:56 -0000    1.35
--- src/bin/psql/print.h    17 Apr 2008 14:05:40 -0000
***************
*** 21,26 ****
--- 21,27 ----
      PRINT_NOTHING = 0,            /* to make sure someone initializes this */
      PRINT_UNALIGNED,
      PRINT_ALIGNED,
+     PRINT_WRAP,
      PRINT_HTML,
      PRINT_LATEX,
      PRINT_TROFF_MS

Re: Proposed patch - psql wraps at window width

From
Alvaro Herrera
Date:
Bryce Nesbitt wrote:
> I've attached a patch, against current 8.4 cvs, which optionally sets a
> maximum width for psql output:

Some random comments:

* Don't use C++ style comments (//).  Some compilers don't like these.

* Beware of brace position: we use braces on their own, indented at the
  start of a new line, so

!     while(--count) {
!         lines++;
!         lines->ptr   = NULL;
!         lines->width = 0;
!         }

becomes


!     while(--count)
!       {
!         lines++;
!         lines->ptr   = NULL;
!         lines->width = 0;
!         }

(with correct indentation anyway)


* Always use tabs, not spaces, to indent.  Tabs are 4 spaces wide.

* Don't use double stars in comments.

* We're not in the habit of giving credit in code comments.  It gets
messy fast.

* Don't lose warning comments like this one (unless you've removed the
assumption of course)

/*
 * Assumption: This code used only on strings
 * without multibyte characters, otherwise
 * this_line->width < strlen(this_ptr) and we get
 * an overflow
 */

In fact I wonder if you've introduced this assumption in the other case
on that code (i.e. when alignment is not 'r').  I'm not seeing any
checks for multibytes in there, but perhaps I'm missing it.


* "} else" is forbidden too.  Use two separate lines.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

Re: Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
Alvaro is correct.  I made most or all of these adjustments in the
updated version I posted yesterday.

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

Alvaro Herrera wrote:
> Bryce Nesbitt wrote:
> > I've attached a patch, against current 8.4 cvs, which optionally sets a
> > maximum width for psql output:
>
> Some random comments:
>
> * Don't use C++ style comments (//).  Some compilers don't like these.
>
> * Beware of brace position: we use braces on their own, indented at the
>   start of a new line, so
>
> !     while(--count) {
> !         lines++;
> !         lines->ptr   = NULL;
> !         lines->width = 0;
> !         }
>
> becomes
>
>
> !     while(--count)
> !       {
> !         lines++;
> !         lines->ptr   = NULL;
> !         lines->width = 0;
> !         }
>
> (with correct indentation anyway)
>
>
> * Always use tabs, not spaces, to indent.  Tabs are 4 spaces wide.
>
> * Don't use double stars in comments.
>
> * We're not in the habit of giving credit in code comments.  It gets
> messy fast.
>
> * Don't lose warning comments like this one (unless you've removed the
> assumption of course)
>
> /*
>  * Assumption: This code used only on strings
>  * without multibyte characters, otherwise
>  * this_line->width < strlen(this_ptr) and we get
>  * an overflow
>  */
>
> In fact I wonder if you've introduced this assumption in the other case
> on that code (i.e. when alignment is not 'r').  I'm not seeing any
> checks for multibytes in there, but perhaps I'm missing it.
>
>
> * "} else" is forbidden too.  Use two separate lines.
>
> --
> Alvaro Herrera                                http://www.CommandPrompt.com/
> The PostgreSQL Company - Command Prompt, Inc.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +

Re: Proposed patch - psql wraps at window width

From
Alvaro Herrera
Date:
Bruce Momjian wrote:
>
> Alvaro is correct.  I made most or all of these adjustments in the
> updated version I posted yesterday.

Doh.  I didn't realize you had posted a new version :-(

People is complaining here that we don't teach people here anyway, so
hopefully my comments were still useful :-)

--
Alvaro Herrera                                http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

Re: Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
Alvaro Herrera wrote:
> Bruce Momjian wrote:
> >
> > Alvaro is correct.  I made most or all of these adjustments in the
> > updated version I posted yesterday.
>
> Doh.  I didn't realize you had posted a new version :-(
>
> People is complaining here that we don't teach people here anyway, so
> hopefully my comments were still useful :-)

Oh, yea, certainly.  I didn't mention it to the author at first because
it was his first patch, and he did a _very_ nice job considering the
complexity of what he was doing.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +

Re: Proposed patch - psql wraps at window width

From
Gregory Stark
Date:
"Peter Eisentraut" <peter_e@gmx.net> writes:

> Bruce Momjian wrote:
>> I checked the use of COLUMNS and it seems bash updates the environment
>> variable when a window is resized.  I added ioctl(TIOCGWINSZ) if COLUMNS
>> isn't set.  We already had a call in print.c for detecting the
>> number of rows on the screen to determine if the pager should
>> be used.  Seems COLUMNS should take precedence over ioctl(), right?
>
> Considering that the code to determine the row count is undisputed so far, the
> column count detection should work the same.  That is, we might not need to
> look at COLUMNS at all.  Unless there is a use case for overriding the column
> count (instead of just turning off the wrapping).

I do that all the time. I normally am running under an emacs terminal so I
don't know what width the ioctl's going to get back but it's unlikely to be
right. In any case I may want to format the output to a width narrower than
the window because I'm going to narrow it.

Also, how would you suggest figuring the width to use for output going to a
file? ioctl is irrelevant in that case, imho it should just default to 80
columns if COLUMNS is unset.

--
  Gregory Stark
  EnterpriseDB          http://www.enterprisedb.com
  Ask me about EnterpriseDB's Slony Replication support!

Re: Proposed patch - psql wraps at window width

From
Tom Lane
Date:
Gregory Stark <stark@enterprisedb.com> writes:
> Also, how would you suggest figuring the width to use for output going to a
> file? ioctl is irrelevant in that case, imho it should just default to 80
> columns if COLUMNS is unset.

It would be a spectacularly awful idea for this patch to affect the
output to a file at all.

            regards, tom lane

Re: Proposed patch - psql wraps at window width

From
Gregory Stark
Date:
"Tom Lane" <tgl@sss.pgh.pa.us> writes:

> Gregory Stark <stark@enterprisedb.com> writes:
>> Also, how would you suggest figuring the width to use for output going to a
>> file? ioctl is irrelevant in that case, imho it should just default to 80
>> columns if COLUMNS is unset.
>
> It would be a spectacularly awful idea for this patch to affect the
> output to a file at all.

It's a compromise of convenience over principle to allow the default output
format to vary but I would still want to have the same set of output formats
_available_ to me regardless of whether I'm redirecting to a file or not. Much
like ls -C is available even if you're redirecting to a file and -1 is
available if you're on a terminal.

It sucks to run a program, decide you want to capture that output and find you
get something else. It *really* sucks to find there's just no way to get the
same output short of heroic efforts.

I also have the converse problem occasionally. I run everything under emacs
and occasionally run into programs which default to awkward output formats.
Usually it's not too bad because it's still on a pty but the window width is a
particular one which confuses programs.

--
  Gregory Stark
  EnterpriseDB          http://www.enterprisedb.com
  Ask me about EnterpriseDB's RemoteDBA services!

Re: Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:
<br /><blockquote cite="mid:871w53bzsi.fsf@oxford.xeocode.com" type="cite"><blockquote type="cite"><pre wrap="">It
wouldbe a spectacularly awful idea for this patch to affect the
 
output to a file at all.   </pre></blockquote><pre wrap="">.....
It sucks to run a program, decide you want to capture that output and find you
get something else. It *really* sucks to find there's just no way to get the
same output short of heroic efforts.</pre></blockquote> I agree with Gregory here: I may want to capture either the
wrappedor unwrapped output to a file or a pipe.<br /> Perhaps the enabling flag for this feature should take a
parameter,which is the number of columns to wrap to.<br /><br /> I was not bold enough to propose that wrapping be the
defaultbehavior for the terminal.<br /> And there's no way I'd want wrapping as the default for pipe output.<br /><br
/>                         -Bryce<br /><br /> 

Re: Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:
<br /><br /> Peter Eisentraut wrote: <blockquote cite="mid:200804171509.25472.peter_e@gmx.net" type="cite"><pre
wrap="">BruceMomjian wrote: </pre><blockquote type="cite"><pre wrap="">I checked the use of COLUMNS and it seems bash
updatesthe environment
 
variable when a window is resized.  I added ioctl(TIOCGWINSZ) if COLUMNS
isn't set.  We already had a call in print.c for detecting the
number of rows on the screen to determine if the pager should
be used.  Seems COLUMNS should take precedence over ioctl(), right?   </pre></blockquote><pre wrap="">
Considering that the code to determine the row count is undisputed so far, the 
column count detection should work the same.  That is, we might not need to 
look at COLUMNS at all.  Unless there is a use case for overriding the column 
count (instead of just turning off the wrapping). </pre></blockquote> I asked the folks over at "Experts Exchange" to
testthe behavior of the ioctl and $COLUMNS on various platforms.  I'd been told that I would face huge problems if a
consolewas resized.  But the results were pretty consistent, and nothing had problems with resize:  <a
class="moz-txt-link-freetext"
href="http://www.experts-exchange.com/Programming/Open_Source/Q_23243646.html">http://www.experts-exchange.com/Programming/Open_Source/Q_23243646.html</a><br
/><br/> It appears impossible to override $COLUMNS, on some platforms as the readline call sets it.<br /> On many
platforms$COLUMNS is null until the call to readline.<br /> OSX does not set $COLUMNS at all.<br /><br />             
           -Bryce<br /><br /> 

Re: Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:
<br /> Bruce Momjian wrote:<br /><blockquote cite="mid:200804171407.m3HE7EW24318@momjian.us" type="cite"><blockquote
type="cite"><prewrap="">I also found that for hugely wide output it was better to give up (do 
 
nothing), rather than mangle the output in a futile attempt to squash it 
to the window width.  So there is one more clause in the wrapping if.   </pre></blockquote><pre wrap="">
Was it because of performance?  I have a way to fix that by decrementing
by more than one to shrink a column?  I am attaching a new patch with my
improved loop.  It remembers the previous maximum ratio.</pre></blockquote> No!  Performance was not the issue.<br />
Theout just looked like a jumble onscreen when the line was word wrapped BUT did not fit on the screen anyway.<br /><br
/>To increase the number of layouts that fit, a co-worker suggested I squeeze out the 2 spaces in each column
header.<br/><br />                                           -Bryce<br /><br /> 

Re: Proposed patch - psql wraps at window width

From
Tom Lane
Date:
Bryce Nesbitt <bryce2@obviously.com> writes:
>     <pre wrap="">I checked the use of COLUMNS and it seems bash updates the environment
> variable when a window is resized.

[ Please get rid of the HTML formatting ... ]

Bash can update the environment all it wants, but that will not affect
what is seen by a program that's already running.  Personally I often
resize the window while psql is running, and I expect that to work.

I'm with Peter on this one: we have used ioctl, and nothing else, to
determine the vertical window dimension for many years now, to the tune
of approximately zero complaints.  It's going to take one hell of a
strong argument to persuade me that determination of the horizontal
dimension should not work exactly the same way.

            regards, tom lane

Re: Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:

Alvaro Herrera wrote:
> * Don't lose warning comments like this one (unless you've removed the
> assumption of course)
>
> /*
>  * Assumption: This code used only on strings
>  * without multibyte characters, otherwise
>  * this_line->width < strlen(this_ptr) and we get
>  * an overflow
>  */
In fact, that particular assumption was causing a problem, causing a
segfault.  I can't be certain, because the multibyte stuff is pretty
intense, but I think I nailed it.  Thanks for all your comments!


Coding standards

From
Bryce Nesbitt
Date:
Alvaro Herrera wrote:
> People [are] complaining here that we don't teach people here anyway, so
> hopefully my comments were still useful :-)
>
Yes they are useful.  As a new patcher, where should I look for coding
standards?  How about a little FAQ at the
top of the CVS source tree?

Though, darn it, I sure like //

And my vi is set to:
  set sw=4
  set ts=4
  set expandtab
Because my corporate projects require spaces not tabs.

> Some random comments:
>
> * Don't use C++ style comments (//).  Some compilers don't like these.
>
> * Beware of brace position: we use braces on their own, indented at the
>   start of a new line, so
>
> !     while(--count) {
> !         lines++;
> !         lines->ptr   = NULL;
> !         lines->width = 0;
> !         }
>
> becomes
>
>
> !     while(--count)
> !       {
> !         lines++;
> !         lines->ptr   = NULL;
> !         lines->width = 0;
> !         }
>
> (with correct indentation anyway)
>
>
> * Always use tabs, not spaces, to indent.  Tabs are 4 spaces wide.
>
> * Don't use double stars in comments.
>
> * "} else" is forbidden too.  Use two separate lines.


Re: Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:
Bruce Momjian wrote:
> I don't think you need to do that to get it applied --- there is nothing
> windows-specific in your code.
>
> Is this ready to be applied?  Do you want to send a final update or are
> you still testing?
>
Looks good, but I suggest adding "give up if the header is too wide":

        if (tcolumns > 0 && tcolumns >= total_header_width)

/*
 * psql - the PostgreSQL interactive terminal
 *
 * Copyright (c) 2000-2008, PostgreSQL Global Development Group
 *
 * $PostgreSQL: pgsql/src/bin/psql/print.c,v 1.97 2008/03/27 03:57:34 tgl Exp $
 */
#include "postgres_fe.h"

#include "print.h"
#include "catalog/pg_type.h"

#include <math.h>
#include <signal.h>
#include <unistd.h>

#ifndef WIN32
#include <sys/ioctl.h>            /* for ioctl() */
#endif

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif

#include <locale.h>

#include "pqsignal.h"

#include "mbprint.h"

/*
 * We define the cancel_pressed flag in this file, rather than common.c where
 * it naturally belongs, because this file is also used by non-psql programs
 * (see the bin/scripts/ directory).  In those programs cancel_pressed will
 * never become set and will have no effect.
 *
 * Note: print.c's general strategy for when to check cancel_pressed is to do
 * so at completion of each row of output.
 */
volatile bool cancel_pressed = false;

static char *decimal_point;
static char *grouping;
static char *thousands_sep;

#define COL_RATIO(col_num) \
            ((double) width_wrap[col_num] / width_average[col_num] + \
            width_max[col_num] * 0.01)        /* Penalize wide columns */

static void *
pg_local_malloc(size_t size)
{
    void       *tmp;

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

static void *
pg_local_calloc(int count, size_t size)
{
    void       *tmp;

    tmp = calloc(count, size);
    if (!tmp)
    {
        fprintf(stderr, _("out of memory\n"));
        exit(EXIT_FAILURE);
    }
    return tmp;
}

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

/* 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);

    if (int_len > 0)
        /* Don't count a leading separator */
        len = (int_len / groupdigits - (int_len % groupdigits == 0)) *
            strlen(thousands_sep);

    if (strchr(my_str, '.') != NULL)
        len += strlen(decimal_point) - strlen(".");

    return len;
}

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

/* Returns the appropriately formatted string in a new allocated block, caller must free */
static char *
format_numeric_locale(const char *my_str)
{
    int            i,
                j,
                int_len = integer_digits(my_str),
                leading_digits;
    int            groupdigits = atoi(grouping);
    int            new_str_start = 0;
    char       *new_str = new_str = pg_local_malloc(
                                     strlen_with_numeric_locale(my_str) + 1);

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

    if (my_str[0] == '-')        /* skip over sign, affects grouping
                                 * calculations */
    {
        new_str[0] = my_str[0];
        my_str++;
        new_str_start = 1;
    }

    for (i = 0, j = new_str_start;; 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];
    }

    return 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 printTableOpt *opt,
                     FILE *fout)
{
    const char *opt_fieldsep = opt->fieldSep;
    const char *opt_recordsep = opt->recordSep;
    bool        opt_tuples_only = opt->tuples_only;
    bool        opt_numeric_locale = opt->numericLocale;
    unsigned int col_count = 0;
    unsigned int i;
    const char *const * ptr;
    bool        need_recordsep = false;

    if (cancel_pressed)
        return;

    if (!opt_fieldsep)
        opt_fieldsep = "";
    if (!opt_recordsep)
        opt_recordsep = "";

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

    if (opt->start_table)
    {
        /* print title */
        if (!opt_tuples_only && title)
            fprintf(fout, "%s%s", title, opt_recordsep);

        /* print headers */
        if (!opt_tuples_only)
        {
            for (ptr = headers; *ptr; ptr++)
            {
                if (ptr != headers)
                    fputs(opt_fieldsep, fout);
                fputs(*ptr, fout);
            }
            need_recordsep = true;
        }
    }
    else
        /* assume continuing printout */
        need_recordsep = true;

    /* print cells */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
        if (need_recordsep)
        {
            fputs(opt_recordsep, fout);
            need_recordsep = false;
            if (cancel_pressed)
                break;
        }
        if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
        {
            char       *my_cell = format_numeric_locale(*ptr);

            fputs(my_cell, fout);
            free(my_cell);
        }
        else
            fputs(*ptr, fout);

        if ((i + 1) % col_count)
            fputs(opt_fieldsep, fout);
        else
            need_recordsep = true;
    }

    /* print footers */
    if (opt->stop_table)
    {
        if (!opt_tuples_only && footers && !cancel_pressed)
            for (ptr = footers; *ptr; ptr++)
            {
                if (need_recordsep)
                {
                    fputs(opt_recordsep, fout);
                    need_recordsep = false;
                }
                fputs(*ptr, fout);
                need_recordsep = true;
            }

        /* the last record needs to be concluded with a newline */
        if (need_recordsep)
            fputc('\n', fout);
    }
}


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 printTableOpt *opt, FILE *fout)
{
    const char *opt_fieldsep = opt->fieldSep;
    const char *opt_recordsep = opt->recordSep;
    bool        opt_tuples_only = opt->tuples_only;
    bool        opt_numeric_locale = opt->numericLocale;
    unsigned int col_count = 0;
    unsigned int i;
    const char *const * ptr;
    bool        need_recordsep = false;

    if (cancel_pressed)
        return;

    if (!opt_fieldsep)
        opt_fieldsep = "";
    if (!opt_recordsep)
        opt_recordsep = "";

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

    if (opt->start_table)
    {
        /* print title */
        if (!opt_tuples_only && title)
        {
            fputs(title, fout);
            need_recordsep = true;
        }
    }
    else
        /* assume continuing printout */
        need_recordsep = true;

    /* print records */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
        if (need_recordsep)
        {
            /* record separator is 2 occurrences of recordsep in this mode */
            fputs(opt_recordsep, fout);
            fputs(opt_recordsep, fout);
            need_recordsep = false;
            if (cancel_pressed)
                break;
        }

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

            fputs(my_cell, fout);
            free(my_cell);
        }
        else
            fputs(*ptr, fout);

        if ((i + 1) % col_count)
            fputs(opt_recordsep, fout);
        else
            need_recordsep = true;
    }

    if (opt->stop_table)
    {
        /* print footers */
        if (!opt_tuples_only && footers && *footers && !cancel_pressed)
        {
            fputs(opt_recordsep, fout);
            for (ptr = footers; *ptr; ptr++)
            {
                fputs(opt_recordsep, fout);
                fputs(*ptr, fout);
            }
        }

        fputc('\n', fout);
    }
}


/********************/
/* Aligned text        */
/********************/


/* draw "line" */
static void
_print_horizontal_line(const unsigned int col_count, const unsigned int *widths, unsigned short border, FILE *fout)
{
    unsigned int i,
                j;

    if (border == 1)
        fputc('-', fout);
    else if (border == 2)
        fputs("+-", fout);

    for (i = 0; i < col_count; i++)
    {
        for (j = 0; j < widths[i]; j++)
            fputc('-', fout);

        if (i < col_count - 1)
        {
            if (border == 0)
                fputc(' ', fout);
            else
                fputs("-+-", fout);
        }
    }

    if (border == 2)
        fputs("-+", fout);
    else if (border == 1)
        fputc('-', fout);

    fputc('\n', fout);
}


/*
 *    Prety pretty boxes around cells.
 */
static void
print_aligned_text(const char *title, const char *const * headers,
                   const char *const * cells, const char *const * footers,
                   const char *opt_align, const printTableOpt *opt,
                   FILE *fout)
{
    bool        opt_tuples_only = opt->tuples_only;
    bool        opt_numeric_locale = opt->numericLocale;
    int            encoding = opt->encoding;
    unsigned short int opt_border = opt->border;

    unsigned int col_count = 0, cell_count = 0;

    unsigned int i,
                j;

    unsigned int *width_header,
               *width_max,
               *width_wrap,
               *width_average;
    unsigned int *heights,
               *format_space;
    unsigned char **format_buf;
    unsigned int width_total;
    unsigned int total_header_width;

    const char *const * ptr;

    struct lineptr **col_lineptrs;        /* pointers to line pointer per column */
    struct lineptr *lineptr_list;        /* complete list of linepointers */

    int           *complete;        /* Array remembering which columns have
                                 * completed output */
    int            tcolumns = 0;    /* Width of interactive console */

    if (cancel_pressed)
        return;

    if (opt_border > 2)
        opt_border = 2;

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

    if (col_count > 0)
    {
        width_header = pg_local_calloc(col_count, sizeof(*width_header));
        width_average = pg_local_calloc(col_count, sizeof(*width_average));
        width_max = pg_local_calloc(col_count, sizeof(*width_max));
        width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap));
        heights = pg_local_calloc(col_count, sizeof(*heights));
        col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
        format_space = pg_local_calloc(col_count, sizeof(*format_space));
        format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
        complete = pg_local_calloc(col_count, sizeof(*complete));
    }
    else
    {
        width_header = NULL;
        width_average = NULL;
        width_max = NULL;
        width_wrap = NULL;
        heights = NULL;
        col_lineptrs = NULL;
        format_space = NULL;
        format_buf = NULL;
        complete = NULL;
    }

    /* scan all column headers, find maximum width */
    for (i = 0; i < col_count; i++)
    {
        /* Get width & height */
        int            width,
                    height,
                    space;

        pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &space);
        if (width > width_max[i])
            width_max[i] = width;
        if (height > heights[i])
            heights[i] = height;
        if (space > format_space[i])
            format_space[i] = space;

        width_header[i] = width;
    }

    /* scan all rows, find maximum width, compute cell_count */
    for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++)
    {
        int            width,
                    height,
                    space;

        /* Get width, ignore height */
        pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &space);
        if (opt_numeric_locale && opt_align[i % col_count] == 'r')
        {
            width += additional_numeric_locale_len(*ptr);
            space += additional_numeric_locale_len(*ptr);
        }

        if (width > width_max[i % col_count])
            width_max[i % col_count] = width;
        if (height > heights[i % col_count])
            heights[i % col_count] = height;
        if (space > format_space[i % col_count])
            format_space[i % col_count] = space;

        width_average[i % col_count] += width;
    }

    /* If we have rows, compute average */
    if (col_count != 0 && cell_count != 0)
    {
        int rows = cell_count / col_count;

        for (i = 0; i < col_count; i++)
            width_average[i % col_count] /= rows;
    }

    /* adjust the total display width based on border style */
    if (opt_border == 0)
        width_total = col_count - 1;
    else if (opt_border == 1)
        width_total = col_count * 3 - 1;
    else
        width_total = col_count * 3 + 1;
    total_header_width = width_total;

    for (i = 0; i < col_count; i++) {
        width_total            += width_max[i];
        total_header_width    += width_header[i];
        }

    /*
     * At this point: width_max[] contains the max width of each column,
     * heights[] contains the max number of lines in each column,
     * format_space[] contains the maximum storage space for formatting
     * strings, width_total contains the giant width sum.  Now we allocate
     * some memory...
     */
    if (col_count > 0)
    {
        int            height_total = 0;
        struct lineptr *lineptr;

        for (i = 0; i < col_count; i++)
            height_total += heights[i];

        lineptr = lineptr_list = pg_local_calloc(height_total, sizeof(*lineptr_list));

        for (i = 0; i < col_count; i++)
        {
            col_lineptrs[i] = lineptr;
            lineptr += heights[i];

            format_buf[i] = pg_local_malloc(format_space[i] + 1);

            col_lineptrs[i]->ptr = format_buf[i];
        }
    }
    else
        lineptr_list = NULL;


    /* Default word wrap to the full width, i.e. no word wrap */
    for (i = 0; i < col_count; i++)
        width_wrap[i] = width_max[i];

    /*
     * Optional optimized word wrap. Shrink columns with a high max/avg ratio.
     * Slighly bias against wider columns (increases chance a narrow column
     * will fit in its cell)
     */
    if (opt->format == PRINT_WRAP)
    {
        /* If we can get the terminal width */
        char       *env = getenv("COLUMNS");

        if (env != NULL)
            tcolumns = atoi(env);
#ifdef TIOCGWINSZ
        else
        {
            struct winsize screen_size;

            if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
                tcolumns = screen_size.ws_col;
        }
#endif

        /*
         * If available columns is positive...
         * and greater than the width of the unshrinkable column headers
         */
        if (tcolumns > 0 && tcolumns >= total_header_width)
        {
            /* While there is still excess width... */
            while (width_total > tcolumns)
            {
                double        max_ratio = 0, prev_max_ratio = 0;
                double        curr_ratio = 0;
                int            worst_col = -1;

                /*
                 *    Find column that has the highest ratio of its maximum
                 *    width compared to its average width.  This tells us which
                 *    column will produce the fewest wrapped values if shortened.
                 *    width_wrap starts as equal to width_max.
                 */
                for (i = 0; i < col_count; i++)
                    if (width_average[i] && width_wrap[i] > width_header[i])
                    {
                        if (COL_RATIO(i) > max_ratio)
                        {
                            prev_max_ratio = max_ratio;
                            max_ratio = curr_ratio;
                            worst_col = i;
                        }
                    }

                /* Exit loop if we can't squeeze any more. */
                if (worst_col < 0)
                    break;

                /* Squeeze the worst column.  Lather, rinse, repeat */
                do
                {
                    width_wrap[worst_col]--;
                    width_total--;
                } while (width_total > tcolumns && COL_RATIO(i) >= prev_max_ratio);
            }
        }
    }

    /* time to output */
    if (opt->start_table)
    {
        /* print title */
        if (title && !opt_tuples_only)
        {
            int            width,
                        height;

            pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL);
            if (width >= width_total)
                fprintf(fout, "%s\n", title);    /* Aligned */
            else
                fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title);        /* Centered */
        }

        /* print headers */
        if (!opt_tuples_only)
        {
            int            cols_todo;
            int            line_count;

            if (opt_border == 2)
                _print_horizontal_line(col_count, width_wrap, opt_border, fout);

            for (i = 0; i < col_count; i++)
                pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]);

            cols_todo = col_count;
            line_count = 0;
            memset(complete, 0, col_count * sizeof(int));
            while (cols_todo)
            {
                if (opt_border == 2)
                    fprintf(fout, "|%c", line_count ? '+' : ' ');
                else if (opt_border == 1)
                    fputc(line_count ? '+' : ' ', fout);

                for (i = 0; i < col_count; i++)
                {
                    unsigned int nbspace;

                    struct lineptr *this_line = col_lineptrs[i] + line_count;

                    if (!complete[i])
                    {
                        nbspace = width_wrap[i] - this_line->width;

                        /* centered */
                        fprintf(fout, "%-*s%s%-*s",
                                nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");

                        if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr)
                        {
                            cols_todo--;
                            complete[i] = 1;
                        }
                    }
                    else
                        fprintf(fout, "%*s", width_wrap[i], "");
                    if (i < col_count - 1)
                    {
                        if (opt_border == 0)
                            fputc(line_count ? '+' : ' ', fout);
                        else
                            fprintf(fout, " |%c", line_count ? '+' : ' ');
                    }
                }
                line_count++;

                if (opt_border == 2)
                    fputs(" |", fout);
                else if (opt_border == 1)
                    fputc(' ', fout);;
                fputc('\n', fout);
            }

            _print_horizontal_line(col_count, width_wrap, opt_border, fout);
        }
    }

    /* print cells */
    for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
    {
        bool        line_todo,
                    cols_todo;
        int            line_count;

        if (cancel_pressed)
            break;

        /*
         * Format each cell.  Format again, it is a numeric formatting locale
         * (e.g. 123,456 vs. 123456)
         */
        for (j = 0; j < col_count; j++)
        {
            pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);
            if (opt_numeric_locale && opt_align[j % col_count] == 'r')
            {
                char       *my_cell;

                my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr);
                strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large
                                                                 * enough... now */
                free(my_cell);
            }
        }

        /* Print rows to console */
        memset(complete, 0, col_count * sizeof(int));
        line_count = 0;
        line_todo = true;
        while (line_todo)
        {
            cols_todo = true;
            while (cols_todo)
            {
                char    border_cell = '*';

                cols_todo = false;

                /* left border */
                if (opt_border == 2)
                    fputs("| ", fout);
                else if (opt_border == 1)
                    fputc(' ', fout);

                /* for each column */
                for (j = 0; j < col_count; j++)
                {
                    struct lineptr *this_line = &col_lineptrs[j][line_count];

                    if (heights[j] <= line_count)        /* Blank column content */
                    {
                        fprintf(fout, "%*s", width_wrap[j], "");
                        border_cell = '|';
                    }
                    else if (opt_align[j] == 'r')        /* Right aligned cell */
                    {
                        int        strlen_remaining = strlen((char *) this_line->ptr + complete[j]);

                        if (strlen_remaining > width_wrap[j])
                        {
                            fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]);
                            complete[j] += width_wrap[j];        /* We've done THIS much */
                            cols_todo = true;    /* And there is more to do... */
                            border_cell = ':';
                        }
                        else
                        {
                            fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, "");
                            fprintf(fout, "%-s", this_line->ptr + complete[j]);
                            complete[j] += strlen_remaining;
                            border_cell = '|';
                        }
                    }
                    else    /* Left aligned cell */
                    {
                        int        strlen_remaining = strlen((char *) this_line->ptr + complete[j]);

                        if (strlen_remaining > width_wrap[j])
                        {
                            fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]);
                            complete[j] += width_wrap[j];        /* We've done THIS much */
                            cols_todo = true;    /* And there is more to do... */
                            border_cell = ':';
                        }
                        else
                        {
                            fprintf(fout, "%-s", this_line->ptr + complete[j]);
                            fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, "");
                            complete[j] += strlen_remaining;
                            border_cell = '|';
                        }
                    }

                    /* print a divider, middle of columns only */
                    if ((j + 1) % col_count)
                    {
                        if (opt_border == 0)
                            fputc(' ', fout);
                        else if (line_count == 0)
                            fprintf(fout, " %c ", border_cell);
                        else
                            fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':');
                    }
                }

                /* end of row border */
                if (opt_border == 2)
                    fprintf(fout, " %c", border_cell);
                fputc('\n', fout);
            }

            /*
             * Check if any columns have line continuations due to \n in the
             * cell.
             */
            line_count++;
            line_todo = false;
            for (j = 0; j < col_count; j++)
            {
                if (line_count < heights[j])
                {
                    if (col_lineptrs[j][line_count].ptr)
                    {
                        line_todo = true;
                        complete[j] = 0;
                    }
                }
            }
        }
    }

    if (opt->stop_table)
    {
        if (opt_border == 2 && !cancel_pressed)
            _print_horizontal_line(col_count, width_wrap, opt_border, fout);

        /* print footers */
        if (footers && !opt_tuples_only && !cancel_pressed)
            for (ptr = footers; *ptr; ptr++)
                fprintf(fout, "%s\n", *ptr);

        /*
         * for some reason MinGW (and MSVC) outputs an extra newline, so this
         * suppresses it
         */
#ifndef WIN32
        fputc('\n', fout);
#endif
    }

    /* clean up */
    free(width_header);
    free(width_average);
    free(width_max);
    free(width_wrap);
    free(heights);
    free(col_lineptrs);
    free(format_space);
    free(complete);
    free(lineptr_list);
    for (i = 0; i < col_count; i++)
        free(format_buf[i]);
    free(format_buf);
}


static void
print_aligned_vertical(const char *title, const char *const * headers,
                       const char *const * cells, const char *const * footers,
                       const char *opt_align, const printTableOpt *opt,
                       FILE *fout)
{
    bool        opt_tuples_only = opt->tuples_only;
    bool        opt_numeric_locale = opt->numericLocale;
    unsigned short int opt_border = opt->border;
    int            encoding = opt->encoding;
    unsigned int col_count = 0;
    unsigned long record = opt->prior_records + 1;
    const char *const * ptr;
    unsigned int i,
                hwidth = 0,
                dwidth = 0,
                hheight = 1,
                dheight = 1,
                hformatsize = 0,
                dformatsize = 0;
    char       *divider;
    unsigned int cell_count = 0;
    struct lineptr *hlineptr,
               *dlineptr;

    if (cancel_pressed)
        return;

    if (opt_border > 2)
        opt_border = 2;

    if (cells[0] == NULL && opt->start_table && opt->stop_table)
    {
        fprintf(fout, _("(No rows)\n"));
        return;
    }

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

    /* Find the maximum dimensions for the headers */
    for (i = 0; i < col_count; i++)
    {
        int            width,
                    height,
                    fs;

        pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs);
        if (width > hwidth)
            hwidth = width;
        if (height > hheight)
            hheight = height;
        if (fs > hformatsize)
            hformatsize = fs;
    }

    /* Count cells, find their lengths */
    for (ptr = cells; *ptr; ptr++)
        cell_count++;

    /* find longest data cell */
    for (i = 0, ptr = cells; *ptr; ptr++, i++)
    {
        int            numeric_locale_len;
        int            width,
                    height,
                    fs;

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

        pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs);
        width += numeric_locale_len;
        if (width > dwidth)
            dwidth = width;
        if (height > dheight)
            dheight = height;
        if (fs > dformatsize)
            dformatsize = fs;
    }

    /*
     * We now have all the information we need to setup the formatting
     * structures
     */
    dlineptr = pg_local_malloc(sizeof(*dlineptr) * dheight);
    hlineptr = pg_local_malloc(sizeof(*hlineptr) * hheight);

    dlineptr->ptr = pg_local_malloc(dformatsize);
    hlineptr->ptr = pg_local_malloc(hformatsize);

    /* make horizontal border */
    divider = pg_local_malloc(hwidth + dwidth + 10);
    divider[0] = '\0';
    if (opt_border == 2)
        strcat(divider, "+-");
    for (i = 0; i < hwidth; i++)
        strcat(divider, opt_border > 0 ? "-" : " ");
    if (opt_border > 0)
        strcat(divider, "-+-");
    else
        strcat(divider, " ");
    for (i = 0; i < dwidth; i++)
        strcat(divider, opt_border > 0 ? "-" : " ");
    if (opt_border == 2)
        strcat(divider, "-+");

    if (opt->start_table)
    {
        /* print title */
        if (!opt_tuples_only && title)
            fprintf(fout, "%s\n", title);
    }

    /* print records */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
        int            line_count,
                    dcomplete,
                    hcomplete;

        if (i % col_count == 0)
        {
            if (cancel_pressed)
                break;
            if (!opt_tuples_only)
            {
                char        record_str[64];
                size_t        record_str_len;

                if (opt_border == 0)
                    snprintf(record_str, 64, "* Record %lu", record++);
                else
                    snprintf(record_str, 64, "[ RECORD %lu ]", record++);
                record_str_len = strlen(record_str);

                if (record_str_len + opt_border > strlen(divider))
                    fprintf(fout, "%.*s%s\n", opt_border, divider, record_str);
                else
                {
                    char       *div_copy = strdup(divider);

                    if (!div_copy)
                    {
                        fprintf(stderr, _("out of memory\n"));
                        exit(EXIT_FAILURE);
                    }

                    strncpy(div_copy + opt_border, record_str, record_str_len);
                    fprintf(fout, "%s\n", div_copy);
                    free(div_copy);
                }
            }
            else if (i != 0 || !opt->start_table || opt_border == 2)
                fprintf(fout, "%s\n", divider);
        }

        /* Format the header */
        pg_wcsformat((unsigned char *) headers[i % col_count],
                strlen(headers[i % col_count]), encoding, hlineptr, hheight);
        /* Format the data */
        pg_wcsformat((unsigned char *) *ptr, strlen(*ptr), encoding, dlineptr, dheight);

        line_count = 0;
        dcomplete = hcomplete = 0;
        while (!dcomplete || !hcomplete)
        {
            if (opt_border == 2)
                fputs("| ", fout);
            if (!hcomplete)
            {
                fprintf(fout, "%-s%*s", hlineptr[line_count].ptr,
                        hwidth - hlineptr[line_count].width, "");

                if (line_count == (hheight - 1) || !hlineptr[line_count + 1].ptr)
                    hcomplete = 1;
            }
            else
                fprintf(fout, "%*s", hwidth, "");

            if (opt_border > 0)
                fprintf(fout, " %c ", (line_count == 0) ? '|' : ':');
            else
                fputs(" ", fout);

            if (!dcomplete)
            {
                if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
                {
                    char       *my_cell = format_numeric_locale((char *) dlineptr[line_count].ptr);

                    if (opt_border < 2)
                        fprintf(fout, "%s\n", my_cell);
                    else
                        fprintf(fout, "%-s%*s |\n", my_cell,
                                (int) (dwidth - strlen(my_cell)), "");
                    free(my_cell);
                }
                else
                {
                    if (opt_border < 2)
                        fprintf(fout, "%s\n", dlineptr[line_count].ptr);
                    else
                        fprintf(fout, "%-s%*s |\n", dlineptr[line_count].ptr,
                                dwidth - dlineptr[line_count].width, "");
                }

                if (line_count == dheight - 1 || !dlineptr[line_count + 1].ptr)
                    dcomplete = 1;
            }
            else
            {
                if (opt_border < 2)
                    fputc('\n', fout);
                else
                    fprintf(fout, "%*s |\n", dwidth, "");
            }
            line_count++;
        }
    }

    if (opt->stop_table)
    {
        if (opt_border == 2 && !cancel_pressed)
            fprintf(fout, "%s\n", divider);

        /* print footers */
        if (!opt_tuples_only && footers && *footers && !cancel_pressed)
        {
            if (opt_border < 2)
                fputc('\n', fout);
            for (ptr = footers; *ptr; ptr++)
                fprintf(fout, "%s\n", *ptr);
        }

        fputc('\n', fout);
    }

    free(divider);
    free(hlineptr->ptr);
    free(dlineptr->ptr);
    free(hlineptr);
    free(dlineptr);
}


/**********************/
/* HTML printing ******/
/**********************/


void
html_escaped_print(const char *in, FILE *fout)
{
    const char *p;
    bool        leading_space = true;

    for (p = in; *p; p++)
    {
        switch (*p)
        {
            case '&':
                fputs("&", fout);
                break;
            case '<':
                fputs("<", fout);
                break;
            case '>':
                fputs(">", fout);
                break;
            case '\n':
                fputs("<br />\n", fout);
                break;
            case '"':
                fputs(""", fout);
                break;
            case ' ':
                /* protect leading space, for EXPLAIN output */
                if (leading_space)
                    fputs(" ", fout);
                else
                    fputs(" ", fout);
                break;
            default:
                fputc(*p, fout);
        }
        if (*p != ' ')
            leading_space = false;
    }
}


static void
print_html_text(const char *title, const char *const * headers,
                const char *const * cells, const char *const * footers,
                const char *opt_align, const printTableOpt *opt,
                FILE *fout)
{
    bool        opt_tuples_only = opt->tuples_only;
    bool        opt_numeric_locale = opt->numericLocale;
    unsigned short int opt_border = opt->border;
    const char *opt_table_attr = opt->tableAttr;
    unsigned int col_count = 0;
    unsigned int i;
    const char *const * ptr;

    if (cancel_pressed)
        return;

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

    if (opt->start_table)
    {
        fprintf(fout, "<table border=\"%d\"", opt_border);
        if (opt_table_attr)
            fprintf(fout, " %s", opt_table_attr);
        fputs(">\n", fout);

        /* print title */
        if (!opt_tuples_only && title)
        {
            fputs("  <caption>", fout);
            html_escaped_print(title, fout);
            fputs("</caption>\n", fout);
        }

        /* print headers */
        if (!opt_tuples_only)
        {
            fputs("  <tr>\n", fout);
            for (ptr = headers; *ptr; ptr++)
            {
                fputs("    <th align=\"center\">", fout);
                html_escaped_print(*ptr, fout);
                fputs("</th>\n", fout);
            }
            fputs("  </tr>\n", fout);
        }
    }

    /* print cells */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
        if (i % col_count == 0)
        {
            if (cancel_pressed)
                break;
            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' && opt_numeric_locale)
        {
            char       *my_cell = format_numeric_locale(*ptr);

            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)
            fputs("  </tr>\n", fout);
    }

    if (opt->stop_table)
    {
        fputs("</table>\n", fout);

        /* print footers */
        if (!opt_tuples_only && footers && *footers && !cancel_pressed)
        {
            fputs("<p>", fout);
            for (ptr = footers; *ptr; ptr++)
            {
                html_escaped_print(*ptr, fout);
                fputs("<br />\n", fout);
            }
            fputs("</p>", fout);
        }

        fputc('\n', fout);
    }
}


static void
print_html_vertical(const char *title, const char *const * headers,
                    const char *const * cells, const char *const * footers,
                    const char *opt_align, const printTableOpt *opt,
                    FILE *fout)
{
    bool        opt_tuples_only = opt->tuples_only;
    bool        opt_numeric_locale = opt->numericLocale;
    unsigned short int opt_border = opt->border;
    const char *opt_table_attr = opt->tableAttr;
    unsigned int col_count = 0;
    unsigned long record = opt->prior_records + 1;
    unsigned int i;
    const char *const * ptr;

    if (cancel_pressed)
        return;

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

    if (opt->start_table)
    {
        fprintf(fout, "<table border=\"%d\"", opt_border);
        if (opt_table_attr)
            fprintf(fout, " %s", opt_table_attr);
        fputs(">\n", fout);

        /* print title */
        if (!opt_tuples_only && title)
        {
            fputs("  <caption>", fout);
            html_escaped_print(title, fout);
            fputs("</caption>\n", fout);
        }
    }

    /* print records */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
        if (i % col_count == 0)
        {
            if (cancel_pressed)
                break;
            if (!opt_tuples_only)
                fprintf(fout,
                        "\n  <tr><td colspan=\"2\" align=\"center\">Record %lu</td></tr>\n",
                        record++);
            else
                fputs("\n  <tr><td colspan=\"2\"> </td></tr>\n", fout);
        }
        fputs("  <tr valign=\"top\">\n"
              "    <th>", fout);
        html_escaped_print(headers[i % col_count], fout);
        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' && opt_numeric_locale)
        {
            char       *my_cell = format_numeric_locale(*ptr);

            html_escaped_print(my_cell, fout);
            free(my_cell);
        }
        else
            html_escaped_print(*ptr, fout);

        fputs("</td>\n    </tr>\n", fout);
    }

    if (opt->stop_table)
    {
        fputs("</table>\n", fout);

        /* print footers */
        if (!opt_tuples_only && footers && *footers && !cancel_pressed)
        {
            fputs("<p>", fout);
            for (ptr = footers; *ptr; ptr++)
            {
                html_escaped_print(*ptr, fout);
                fputs("<br />\n", fout);
            }
            fputs("</p>", fout);
        }

        fputc('\n', fout);
    }
}


/*************************/
/* LaTeX                 */
/*************************/


static void
latex_escaped_print(const char *in, FILE *fout)
{
    const char *p;

    for (p = in; *p; p++)
        switch (*p)
        {
            case '&':
                fputs("\\&", fout);
                break;
            case '%':
                fputs("\\%", fout);
                break;
            case '$':
                fputs("\\$", fout);
                break;
            case '_':
                fputs("\\_", fout);
                break;
            case '{':
                fputs("\\{", fout);
                break;
            case '}':
                fputs("\\}", fout);
                break;
            case '\\':
                fputs("\\backslash", fout);
                break;
            case '\n':
                fputs("\\\\", fout);
                break;
            default:
                fputc(*p, fout);
        }
}


static void
print_latex_text(const char *title, const char *const * headers,
                 const char *const * cells, const char *const * footers,
                 const char *opt_align, const printTableOpt *opt,
                 FILE *fout)
{
    bool        opt_tuples_only = opt->tuples_only;
    bool        opt_numeric_locale = opt->numericLocale;
    unsigned short int opt_border = opt->border;
    unsigned int col_count = 0;
    unsigned int i;
    const char *const * ptr;

    if (cancel_pressed)
        return;

    if (opt_border > 2)
        opt_border = 2;

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

    if (opt->start_table)
    {
        /* print title */
        if (!opt_tuples_only && title)
        {
            fputs("\\begin{center}\n", fout);
            latex_escaped_print(title, fout);
            fputs("\n\\end{center}\n\n", fout);
        }

        /* begin environment and set alignments and borders */
        fputs("\\begin{tabular}{", fout);

        if (opt_border == 2)
            fputs("| ", fout);
        for (i = 0; i < col_count; i++)
        {
            fputc(*(opt_align + i), fout);
            if (opt_border != 0 && i < col_count - 1)
                fputs(" | ", fout);
        }
        if (opt_border == 2)
            fputs(" |", fout);

        fputs("}\n", fout);

        if (!opt_tuples_only && opt_border == 2)
            fputs("\\hline\n", fout);

        /* print headers */
        if (!opt_tuples_only)
        {
            for (i = 0, ptr = headers; i < col_count; i++, ptr++)
            {
                if (i != 0)
                    fputs(" & ", fout);
                fputs("\\textit{", fout);
                latex_escaped_print(*ptr, fout);
                fputc('}', fout);
            }
            fputs(" \\\\\n", fout);
            fputs("\\hline\n", fout);
        }
    }

    /* print cells */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
        if (opt_numeric_locale)
        {
            char       *my_cell = format_numeric_locale(*ptr);

            latex_escaped_print(my_cell, fout);
            free(my_cell);
        }
        else
            latex_escaped_print(*ptr, fout);

        if ((i + 1) % col_count == 0)
        {
            fputs(" \\\\\n", fout);
            if (cancel_pressed)
                break;
        }
        else
            fputs(" & ", fout);
    }

    if (opt->stop_table)
    {
        if (opt_border == 2)
            fputs("\\hline\n", fout);

        fputs("\\end{tabular}\n\n\\noindent ", fout);

        /* print footers */
        if (footers && !opt_tuples_only && !cancel_pressed)
        {
            for (ptr = footers; *ptr; ptr++)
            {
                latex_escaped_print(*ptr, fout);
                fputs(" \\\\\n", fout);
            }
        }

        fputc('\n', fout);
    }
}


static void
print_latex_vertical(const char *title, const char *const * headers,
                     const char *const * cells, const char *const * footers,
                     const char *opt_align, const printTableOpt *opt,
                     FILE *fout)
{
    bool        opt_tuples_only = opt->tuples_only;
    bool        opt_numeric_locale = opt->numericLocale;
    unsigned short int opt_border = opt->border;
    unsigned int col_count = 0;
    unsigned long record = opt->prior_records + 1;
    unsigned int i;
    const char *const * ptr;

    (void) opt_align;            /* currently unused parameter */

    if (cancel_pressed)
        return;

    if (opt_border > 2)
        opt_border = 2;

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

    if (opt->start_table)
    {
        /* print title */
        if (!opt_tuples_only && title)
        {
            fputs("\\begin{center}\n", fout);
            latex_escaped_print(title, fout);
            fputs("\n\\end{center}\n\n", fout);
        }

        /* begin environment and set alignments and borders */
        fputs("\\begin{tabular}{", fout);
        if (opt_border == 0)
            fputs("cl", fout);
        else if (opt_border == 1)
            fputs("c|l", fout);
        else if (opt_border == 2)
            fputs("|c|l|", fout);
        fputs("}\n", fout);
    }

    /* print records */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
        /* new record */
        if (i % col_count == 0)
        {
            if (cancel_pressed)
                break;
            if (!opt_tuples_only)
            {
                if (opt_border == 2)
                {
                    fputs("\\hline\n", fout);
                    fprintf(fout, "\\multicolumn{2}{|c|}{\\textit{Record %lu}} \\\\\n", record++);
                }
                else
                    fprintf(fout, "\\multicolumn{2}{c}{\\textit{Record %lu}} \\\\\n", record++);
            }
            if (opt_border >= 1)
                fputs("\\hline\n", fout);
        }

        latex_escaped_print(headers[i % col_count], fout);
        fputs(" & ", fout);
        latex_escaped_print(*ptr, fout);
        fputs(" \\\\\n", fout);
    }

    if (opt->stop_table)
    {
        if (opt_border == 2)
            fputs("\\hline\n", fout);

        fputs("\\end{tabular}\n\n\\noindent ", fout);

        /* print footers */
        if (footers && !opt_tuples_only && !cancel_pressed)
        {
            for (ptr = footers; *ptr; ptr++)
            {
                if (opt_numeric_locale)
                {
                    char       *my_cell = format_numeric_locale(*ptr);

                    latex_escaped_print(my_cell, fout);
                    free(my_cell);
                }
                else
                    latex_escaped_print(*ptr, fout);
                fputs(" \\\\\n", fout);
            }
        }

        fputc('\n', fout);
    }
}


/*************************/
/* Troff -ms         */
/*************************/


static void
troff_ms_escaped_print(const char *in, FILE *fout)
{
    const char *p;

    for (p = in; *p; p++)
        switch (*p)
        {
            case '\\':
                fputs("\\(rs", fout);
                break;
            default:
                fputc(*p, fout);
        }
}


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, const printTableOpt *opt,
                    FILE *fout)
{
    bool        opt_tuples_only = opt->tuples_only;
    bool        opt_numeric_locale = opt->numericLocale;
    unsigned short int opt_border = opt->border;
    unsigned int col_count = 0;
    unsigned int i;
    const char *const * ptr;

    if (cancel_pressed)
        return;

    if (opt_border > 2)
        opt_border = 2;

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

    if (opt->start_table)
    {
        /* print title */
        if (!opt_tuples_only && title)
        {
            fputs(".LP\n.DS C\n", fout);
            troff_ms_escaped_print(title, fout);
            fputs("\n.DE\n", fout);
        }

        /* begin environment and set alignments and borders */
        fputs(".LP\n.TS\n", fout);
        if (opt_border == 2)
            fputs("center box;\n", fout);
        else
            fputs("center;\n", fout);

        for (i = 0; i < col_count; i++)
        {
            fputc(*(opt_align + i), fout);
            if (opt_border > 0 && i < col_count - 1)
                fputs(" | ", fout);
        }
        fputs(".\n", fout);

        /* print headers */
        if (!opt_tuples_only)
        {
            for (i = 0, ptr = headers; i < col_count; i++, ptr++)
            {
                if (i != 0)
                    fputc('\t', fout);
                fputs("\\fI", fout);
                troff_ms_escaped_print(*ptr, fout);
                fputs("\\fP", fout);
            }
            fputs("\n_\n", fout);
        }
    }

    /* print cells */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
        if (opt_numeric_locale)
        {
            char       *my_cell = format_numeric_locale(*ptr);

            troff_ms_escaped_print(my_cell, fout);
            free(my_cell);
        }
        else
            troff_ms_escaped_print(*ptr, fout);

        if ((i + 1) % col_count == 0)
        {
            fputc('\n', fout);
            if (cancel_pressed)
                break;
        }
        else
            fputc('\t', fout);
    }

    if (opt->stop_table)
    {
        fputs(".TE\n.DS L\n", fout);

        /* print footers */
        if (footers && !opt_tuples_only && !cancel_pressed)
            for (ptr = footers; *ptr; ptr++)
            {
                troff_ms_escaped_print(*ptr, fout);
                fputc('\n', fout);
            }

        fputs(".DE\n", fout);
    }
}


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, const printTableOpt *opt,
                        FILE *fout)
{
    bool        opt_tuples_only = opt->tuples_only;
    bool        opt_numeric_locale = opt->numericLocale;
    unsigned short int opt_border = opt->border;
    unsigned int col_count = 0;
    unsigned long record = opt->prior_records + 1;
    unsigned int i;
    const char *const * ptr;
    unsigned short current_format = 0;    /* 0=none, 1=header, 2=body */

    (void) opt_align;            /* currently unused parameter */

    if (cancel_pressed)
        return;

    if (opt_border > 2)
        opt_border = 2;

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

    if (opt->start_table)
    {
        /* print title */
        if (!opt_tuples_only && title)
        {
            fputs(".LP\n.DS C\n", fout);
            troff_ms_escaped_print(title, fout);
            fputs("\n.DE\n", fout);
        }

        /* begin environment and set alignments and borders */
        fputs(".LP\n.TS\n", fout);
        if (opt_border == 2)
            fputs("center box;\n", fout);
        else
            fputs("center;\n", fout);

        /* basic format */
        if (opt_tuples_only)
            fputs("c l;\n", fout);
    }
    else
        current_format = 2;        /* assume tuples printed already */

    /* print records */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
        /* new record */
        if (i % col_count == 0)
        {
            if (cancel_pressed)
                break;
            if (!opt_tuples_only)
            {
                if (current_format != 1)
                {
                    if (opt_border == 2 && record > 1)
                        fputs("_\n", fout);
                    if (current_format != 0)
                        fputs(".T&\n", fout);
                    fputs("c s.\n", fout);
                    current_format = 1;
                }
                fprintf(fout, "\\fIRecord %lu\\fP\n", record++);
            }
            if (opt_border >= 1)
                fputs("_\n", fout);
        }

        if (!opt_tuples_only)
        {
            if (current_format != 2)
            {
                if (current_format != 0)
                    fputs(".T&\n", fout);
                if (opt_border != 1)
                    fputs("c l.\n", fout);
                else
                    fputs("c | l.\n", fout);
                current_format = 2;
            }
        }

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

            troff_ms_escaped_print(my_cell, fout);
            free(my_cell);
        }
        else
            troff_ms_escaped_print(*ptr, fout);

        fputc('\n', fout);
    }

    if (opt->stop_table)
    {
        fputs(".TE\n.DS L\n", fout);

        /* print footers */
        if (footers && !opt_tuples_only && !cancel_pressed)
            for (ptr = footers; *ptr; ptr++)
            {
                troff_ms_escaped_print(*ptr, fout);
                fputc('\n', fout);
            }

        fputs(".DE\n", fout);
    }
}


/********************************/
/* Public functions        */
/********************************/


/*
 * PageOutput
 *
 * Tests if pager is needed and returns appropriate FILE pointer.
 */
FILE *
PageOutput(int lines, unsigned short int pager)
{
    /* check whether we need / can / are supposed to use pager */
    if (pager
#ifndef WIN32
        &&
        isatty(fileno(stdin)) &&
        isatty(fileno(stdout))
#endif
        )
    {
        const char *pagerprog;
        FILE       *pagerpipe;

#ifdef TIOCGWINSZ
        int            result;
        struct winsize screen_size;

        result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);

        /* >= accounts for a one-line prompt */
        if (result == -1 || lines >= screen_size.ws_row || pager > 1)
        {
#endif
            pagerprog = getenv("PAGER");
            if (!pagerprog)
                pagerprog = DEFAULT_PAGER;
#ifndef WIN32
            pqsignal(SIGPIPE, SIG_IGN);
#endif
            pagerpipe = popen(pagerprog, "w");
            if (pagerpipe)
                return pagerpipe;
#ifdef TIOCGWINSZ
        }
#endif
    }

    return stdout;
}

/*
 * ClosePager
 *
 * Close previously opened pager pipe, if any
 */
void
ClosePager(FILE *pagerpipe)
{
    if (pagerpipe && pagerpipe != stdout)
    {
        /*
         * If printing was canceled midstream, warn about it.
         *
         * Some pagers like less use Ctrl-C as part of their command set. Even
         * so, we abort our processing and warn the user what we did.  If the
         * pager quit as a result of the SIGINT, this message won't go
         * anywhere ...
         */
        if (cancel_pressed)
            fprintf(pagerpipe, _("Interrupted\n"));

        pclose(pagerpipe);
#ifndef WIN32
        pqsignal(SIGPIPE, SIG_DFL);
#endif
    }
}


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)
{
    static const char *default_footer[] = {NULL};
    FILE       *output;

    if (cancel_pressed)
        return;

    if (opt->format == PRINT_NOTHING)
        return;

    if (!footers)
        footers = default_footer;

    if (fout == stdout)
    {
        int            col_count = 0,
                    row_count = 0,
                    lines;
        const char *const * ptr;

        /* rough estimate of columns and rows */
        if (headers)
            for (ptr = headers; *ptr; ptr++)
                col_count++;
        if (cells)
            for (ptr = cells; *ptr; ptr++)
                row_count++;
        if (col_count > 0)
            row_count /= col_count;

        if (opt->expanded)
            lines = (col_count + 1) * row_count;
        else
            lines = row_count + 1;
        if (footers && !opt->tuples_only)
            for (ptr = footers; *ptr; ptr++)
                lines++;
        output = PageOutput(lines, opt->pager);
    }
    else
        output = fout;

    /* print the stuff */

    if (flog)
        print_aligned_text(title, headers, cells, footers, align,
                           opt, flog);

    switch (opt->format)
    {
        case PRINT_UNALIGNED:
            if (opt->expanded)
                print_unaligned_vertical(title, headers, cells, footers, align,
                                         opt, output);
            else
                print_unaligned_text(title, headers, cells, footers, align,
                                     opt, output);
            break;
        case PRINT_ALIGNED:
        case PRINT_WRAP:
            if (opt->expanded)
                print_aligned_vertical(title, headers, cells, footers, align,
                                       opt, output);
            else
                print_aligned_text(title, headers, cells, footers, align,
                                   opt, output);
            break;
        case PRINT_HTML:
            if (opt->expanded)
                print_html_vertical(title, headers, cells, footers, align,
                                    opt, output);
            else
                print_html_text(title, headers, cells, footers, align,
                                opt, output);
            break;
        case PRINT_LATEX:
            if (opt->expanded)
                print_latex_vertical(title, headers, cells, footers, align,
                                     opt, output);
            else
                print_latex_text(title, headers, cells, footers, align,
                                 opt, output);
            break;
        case PRINT_TROFF_MS:
            if (opt->expanded)
                print_troff_ms_vertical(title, headers, cells, footers, align,
                                        opt, output);
            else
                print_troff_ms_text(title, headers, cells, footers, align,
                                    opt, output);
            break;
        default:
            fprintf(stderr, _("invalid output format (internal error): %d"), opt->format);
            exit(EXIT_FAILURE);
    }

    /* Only close if we used the pager */
    if (output != fout)
        ClosePager(output);
}


void
printQuery(const PGresult *result, const printQueryOpt *opt, FILE *fout, FILE *flog)
{
    int            ntuples;
    int            nfields;
    int            ncells;
    const char **headers;
    const char **cells;
    char      **footers;
    char       *align;
    int            i,
                r,
                c;

    if (cancel_pressed)
        return;

    /* extract headers */
    ntuples = PQntuples(result);
    nfields = PQnfields(result);

    headers = pg_local_calloc(nfields + 1, sizeof(*headers));

    for (i = 0; i < nfields; i++)
    {
        headers[i] = (char *) mbvalidate((unsigned char *) PQfname(result, i),
                                         opt->topt.encoding);
#ifdef ENABLE_NLS
        if (opt->trans_headers)
            headers[i] = _(headers[i]);
#endif
    }

    /* set cells */
    ncells = ntuples * nfields;
    cells = pg_local_calloc(ncells + 1, sizeof(*cells));

    i = 0;
    for (r = 0; r < ntuples; r++)
    {
        for (c = 0; c < nfields; c++)
        {
            if (PQgetisnull(result, r, c))
                cells[i] = opt->nullPrint ? opt->nullPrint : "";
            else
            {
                cells[i] = (char *)
                    mbvalidate((unsigned char *) PQgetvalue(result, r, c),
                               opt->topt.encoding);
#ifdef ENABLE_NLS
                if (opt->trans_columns && opt->trans_columns[c])
                    cells[i] = _(cells[i]);
#endif
            }
            i++;
        }
    }

    /* set footers */

    if (opt->footers)
        footers = opt->footers;
    else if (!opt->topt.expanded && opt->default_footer)
    {
        unsigned long total_records;

        footers = pg_local_calloc(2, sizeof(*footers));
        footers[0] = pg_local_malloc(100);
        total_records = opt->topt.prior_records + ntuples;
        if (total_records == 1)
            snprintf(footers[0], 100, _("(1 row)"));
        else
            snprintf(footers[0], 100, _("(%lu rows)"), total_records);
    }
    else
        footers = NULL;

    /* set alignment */
    align = pg_local_calloc(nfields + 1, sizeof(*align));

    for (i = 0; i < nfields; i++)
    {
        Oid            ftype = PQftype(result, i);

        switch (ftype)
        {
            case INT2OID:
            case INT4OID:
            case INT8OID:
            case FLOAT4OID:
            case FLOAT8OID:
            case NUMERICOID:
            case OIDOID:
            case XIDOID:
            case CIDOID:
            case CASHOID:
                align[i] = 'r';
                break;
            default:
                align[i] = 'l';
                break;
        }
    }

    /* call table printer */
    printTable(opt->title, headers, cells,
               (const char *const *) footers,
               align, &opt->topt, fout, flog);

    free(headers);
    free(cells);
    if (footers && !opt->footers)
    {
        free(footers[0]);
        free(footers);
    }
    free(align);
}


void
setDecimalLocale(void)
{
    struct lconv *extlconv;

    extlconv = localeconv();

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

    /* similar code exists in formatting.c */
    if (*extlconv->thousands_sep)
        thousands_sep = strdup(extlconv->thousands_sep);
    /* Make sure thousands separator doesn't match decimal point symbol. */
    else if (strcmp(decimal_point, ",") != 0)
        thousands_sep = ",";
    else
        thousands_sep = ".";
}

Re: Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:
Peter Eisentraut wrote:
> Bruce Momjian wrote:
>
>> I checked the use of COLUMNS and it seems bash updates the environment
>> variable when a window is resized.  I added ioctl(TIOCGWINSZ) if COLUMNS
>> isn't set.  We already had a call in print.c for detecting the
>> number of rows on the screen to determine if the pager should
>> be used.  Seems COLUMNS should take precedence over ioctl(), right?
>
> Considering that the code to determine the row count is undisputed so far, the
> column count detection should work the same.  That is, we might not need to
> look at COLUMNS at all.  Unless there is a use case for overriding the column
> count (instead of just turning off the wrapping).
>
I asked the folks over at "Experts Exchange" to test the behavior of the
ioctl and $COLUMNS on various platforms.  I'd been told that I would
face huge problems if a console was resized.  But the results were
pretty consistent, and nothing had problems with resize:
http://www.experts-exchange.com/Programming/Open_Source/Q_23243646.html

It appears impossible to override $COLUMNS, on some platforms as the
readline call sets it.
On many platforms $COLUMNS is null until the call to readline.
OSX does not set $COLUMNS at all.

In short, I recommend the ioctl instead.  In order to provide a way to
wrap output to a pipe, I think a different mechanism will have to be found.

                         -Bryce


Re: Coding standards

From
Magnus Hagander
Date:
Bryce Nesbitt wrote:
> Alvaro Herrera wrote:
> > People [are] complaining here that we don't teach people here
> > anyway, so hopefully my comments were still useful :-)
> >
> Yes they are useful.  As a new patcher, where should I look for
> coding standards?  How about a little FAQ at the
> top of the CVS source tree?
>
> Though, darn it, I sure like //
>
> And my vi is set to:
>   set sw=4
>   set ts=4
>   set expandtab
> Because my corporate projects require spaces not tabs.

See the developer FAQ on the website. IIRC, it even contains what you
should put in your vi config file to make it do the right thing...

//Magnus

Re: Proposed patch - psql wraps at window width

From
Gregory Stark
Date:
"Tom Lane" <tgl@sss.pgh.pa.us> writes:

> Bryce Nesbitt <bryce2@obviously.com> writes:
>>     <pre wrap="">I checked the use of COLUMNS and it seems bash updates the environment
>> variable when a window is resized.
>
> [ Please get rid of the HTML formatting ... ]
>
> Bash can update the environment all it wants, but that will not affect
> what is seen by a program that's already running.  Personally I often
> resize the window while psql is running, and I expect that to work.

Hm, then having COLUMNS override the ioctl isn't such a good idea. Checking
GNU ls source the ioctl overrides COLUMNS if it works, but there's a --width
option which trumps both of the other two. I guess just a psql variable would
be the equivalent.

> I'm with Peter on this one: we have used ioctl, and nothing else, to
> determine the vertical window dimension for many years now, to the tune
> of approximately zero complaints.  It's going to take one hell of a
> strong argument to persuade me that determination of the horizontal
> dimension should not work exactly the same way.

Well the cases are not analogous. Firstly, the window height doesn't actually
alter the output. Secondly there's really no downside in a false positive
since most pagers just exit if they decide the output fit on the screen --
which probably explains why no ssh users have complained... And in any case
you can always override it by piping the output to a pager yourself -- which
is effectively all I'm suggesting doing here.

So here are your two hella-strong arguments:

a) not all terminals support the ioctl. Emacs shell users may be eccentric but
   surely using psql over ssh isn't especially uncommon. Falling back to
   COLUMNS is standard, GNU ls is not alone, Solaris and FreeBSD both document
   supporting COLUMNS.

b) you don't necessarily *want* the output formatted to fill the screen. You
   may be generating a report to email and want to set the width to the RFC
   recommended 72 characters. You may just have a full screen terminal but not
   enjoy reading 200-character long lines -- try it, it's really hard:

       MANWIDTH
              If  $MANWIDTH  is  set,  its value is used as the line length for which manual pages should be formatted.
If it is not set, manual pages will be formatted with a line length appropriate to the 
              current terminal (using an ioctl(2) if available, the value of $COLUMNS, or falling back to 80 characters
ifneither is available).  Cat pages will only be saved when the default formatting can 
              be used, that is when the terminal line length is between 66 and 80 characters.

--
  Gregory Stark
  EnterpriseDB          http://www.enterprisedb.com
  Ask me about EnterpriseDB's On-Demand Production Tuning

Re: Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:
Peter Eisentraut wrote:
> Bruce Momjian wrote:
>
>> I checked the use of COLUMNS and it seems bash updates the environment
>> variable when a window is resized.  I added ioctl(TIOCGWINSZ) if COLUMNS
>> isn't set.  We already had a call in print.c for detecting the
>> number of rows on the screen to determine if the pager should
>> be used.  Seems COLUMNS should take precedence over ioctl(), right?
>
> Considering that the code to determine the row count is undisputed so far, the
> column count detection should work the same.  That is, we might not need to
> look at COLUMNS at all.  Unless there is a use case for overriding the column
> count (instead of just turning off the wrapping).
>
I asked the folks over at "Experts Exchange" to test the behavior of the
ioctl and $COLUMNS on various platforms.  I'd been told that I would
face huge problems if a console was resized.  But the results were
pretty consistent, and $COLUMNS had no problems with resize:
http://www.experts-exchange.com/Programming/Open_Source/Q_23243646.html

But appears impossible to override $COLUMNS, on some platforms as the
readline call sets it.
On many platforms $COLUMNS is null until the call to readline.
OSX does not set $COLUMNS at all.

So I think the ioctl should be used to determine the wrap width for
terminals, and some other mechanism used for pipes.

There's no way I'd want wrapping as the default for pipe output.  I was
not bold enough to propose that wrapping be the default behavior for the
terminal.

                         -Bryce


Re: Proposed patch - psql wraps at window width

From
Gregory Stark
Date:
"Bryce Nesbitt" <bryce2@obviously.com> writes:

> I asked the folks over at "Experts Exchange" to test the behavior of the ioctl

I always thought that was a joke domain name, like Pen Island.com.

--
  Gregory Stark
  EnterpriseDB          http://www.enterprisedb.com
  Ask me about EnterpriseDB's PostGIS support!

Re: Coding standards

From
Alvaro Herrera
Date:
Bryce Nesbitt wrote:
> Alvaro Herrera wrote:
>> People [are] complaining here that we don't teach people here anyway, so
>> hopefully my comments were still useful :-)
>>
> Yes they are useful.  As a new patcher, where should I look for coding
> standards?  How about a little FAQ at the
> top of the CVS source tree?

The developer's FAQ is supposed to contain this kind of thing, but I
think it's rather thin on actual details.  (Some time ago it was
proposed to create a "style guide", but people here thought it was a
waste of time and "it will not cover what's really important", so we're
stuck with repeating the advice over and over.)

> Though, darn it, I sure like //
>
> And my vi is set to:
>  set sw=4
>  set ts=4
>  set expandtab
> Because my corporate projects require spaces not tabs.

I use this:

:if match(getcwd(), "/home/alvherre/Code/CVS/pgsql") == 0
:  set cinoptions=(0
:  set tabstop=4
:  set shiftwidth=4
:  let $CSCOPE_DB=substitute(getcwd(), "^\\(.*/pgsql/source/[^/]*\\)/.*", "\\1", "")
:  let &tags=substitute(getcwd(), "^\\(.*/pgsql/source/[^/]*\\)/.*", "\\1", "") . "/tags"
:  let &path=substitute(getcwd(), "^\\(.*/pgsql/source/[^/]*\\)/.*", "\\1", "") . "/src/include"
:endif

Of course, you need to adjust the paths.  The cscope, tags and path
things let me quickly navigate through the files, but they don't affect
the editor behavior.

I never set expandtab so it's not a problem for me, but I guess you can
do ":set noexpandtab" in that block to reset it for Postgres use.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Re: Proposed patch - psql wraps at window width

From
"Joshua D. Drake"
Date:
On Fri, 18 Apr 2008 17:21:26 +0100
Gregory Stark <stark@enterprisedb.com> wrote:

> "Bryce Nesbitt" <bryce2@obviously.com> writes:
>
> > I asked the folks over at "Experts Exchange" to test the behavior
> > of the ioctl
>
> I always thought that was a joke domain name, like Pen Island.com.
>

Its not and a lot of postgresql users interact there.

Joshua D. Drake


--
The PostgreSQL Company since 1997: http://www.commandprompt.com/
PostgreSQL Community Conference: http://www.postgresqlconference.org/
United States PostgreSQL Association: http://www.postgresql.us/
Donate to the PostgreSQL Project: http://www.postgresql.org/about/donate



Re: Coding standards

From
Chris Browne
Date:
bryce2@obviously.com (Bryce Nesbitt) writes:
> Alvaro Herrera wrote:
>> People [are] complaining here that we don't teach people here anyway, so
>> hopefully my comments were still useful :-)
>>
> Yes they are useful.  As a new patcher, where should I look for coding
> standards?  How about a little FAQ at the
> top of the CVS source tree?
>
> Though, darn it, I sure like //
>
> And my vi is set to:
>  set sw=4
>  set ts=4
>  set expandtab
> Because my corporate projects require spaces not tabs.

Note that you can find config for vim and emacs to get them to support the coding standards in:

/opt/src/pgsql-HEAD/src/tools/editors/emacs.samples
/opt/src/pgsql-HEAD/src/tools/editors/vim.samples

For vim, the essentials are thus:

:if match(getcwd(), "/pgsql") >=0 ||  match(getcwd(), "/postgresql") >= 0
:  set cinoptions=(0
:  set tabstop=4
:  set shiftwidth=4
:endif

The hooks are slightly different (though not by spectacularly much,
somewhat surprisingly) for Emacs...
--
let name="cbbrowne" and tld="cbbrowne.com" in name ^ "@" ^ tld;;
http://cbbrowne.com/info/advocacy.html
"A language that doesn't affect the way you think about programming,
is not worth knowing."  -- Alan J. Perlis

Re: Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
Bryce Nesbitt wrote:
> Bruce Momjian wrote:
> > I don't think you need to do that to get it applied --- there is nothing
> > windows-specific in your code.
> >
> > Is this ready to be applied?  Do you want to send a final update or are
> > you still testing?
> >
> Looks good, but I suggest adding "give up if the header is too wide":
>
>         if (tcolumns > 0 && tcolumns >= total_header_width)

OK, I have created a new version of the patch, attached.  Bryce, I
included your code line above and the other changes you had in the new
version of print.c.

I also set it to use ioctl() width if the output is to the terminal, and
\pset column for other cases, and adjusted the documentation.

I played a little with how wrapping would look with values that
contained newlines:

    test=> \pset format wrapped
    Output format is wrapped.

    test=> \pset columns 70
    Target column width for "wrap" format is 70.

    test=> select 1, 2, repeat('a', 80), repeat('b', 80), E'a\nb\nc', 1 from
    generate_series(1,2)\g /rtmp/d


     ?column? | ?column? |   repeat   |   repeat    | ?column? | ?column?
    ----------+----------+------------+-------------+----------+----------
            1 |        2 | aaaaaaaaaa | bbbbbbbbbbb | a        |        1
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbb
                                                    : b
                                                    : c
            1 |        2 | aaaaaaaaaa | bbbbbbbbbbb | a        |        1
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbbbbbbbbbb
                         ; aaaaaaaaaa ; bbb
                                                    : b
                                                    : c
    (2 rows)

Notice that the marker for wrapping is now ";".  The marker is always on
the left edge, assuming you are not in the first column, in which case
there is no marker.  This is how newline behaves and I copied that.
Also note that there is no separator for columns that don't wrap down
far enough.  This is also how the newline case is handled.  Is this the
way we want it to display?  (Bryce, I am not sure your original code
handled this right.)

So, a query with only one column is going to be unclear whether it has
embedded newlines or wrapping has happening. One idea is for wrapping to
place a dash at the end of the value, so you have:

    aaaaaaaaaa-
    aaaaaaaaaa-
    aaaa

or something like that.

Another issue is whether newlines should filter up into the rows already
used for wrapping in adjacent columns.  Right now it doesn't do that but
we could.

I found a problem with the original patch related to multibyte. The code
was fine up until the wrapping happened, at which point 'width' was
assumed to equal characters and the value was chopped into width "byte"
chunks.  It has to be done in width "character" chunks so each chunk has
the same number of characters and you don't split a multi-byte character
across a line.  I fixed it by creating a new function
mb_strlen_max_width().

I also restructured some of the code and added comments.

Community, I am looking for feedback on how to handle some of my
questions above.

Bryce, I am sorry this patch is taking so many iterations but the
feature has to work perfectly in lots of complex configurations so it
takes longer to complete and apply.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +
Index: doc/src/sgml/ref/psql-ref.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v
retrieving revision 1.199
diff -c -c -r1.199 psql-ref.sgml
*** doc/src/sgml/ref/psql-ref.sgml    30 Mar 2008 18:10:20 -0000    1.199
--- doc/src/sgml/ref/psql-ref.sgml    20 Apr 2008 03:53:39 -0000
***************
*** 1513,1519 ****
            <listitem>
            <para>
            Sets the output format to one of <literal>unaligned</literal>,
!           <literal>aligned</literal>, <literal>html</literal>,
            <literal>latex</literal>, or <literal>troff-ms</literal>.
            Unique abbreviations are allowed.  (That would mean one letter
            is enough.)
--- 1513,1520 ----
            <listitem>
            <para>
            Sets the output format to one of <literal>unaligned</literal>,
!           <literal>aligned</literal>, <literal>wrapped</literal>,
!           <literal>html</literal>,
            <literal>latex</literal>, or <literal>troff-ms</literal>.
            Unique abbreviations are allowed.  (That would mean one letter
            is enough.)
***************
*** 1525,1532 ****
            is intended to create output that might be intended to be read
            in by other programs (tab-separated, comma-separated).
            <quote>Aligned</quote> mode is the standard, human-readable,
!           nicely formatted text output that is default. The
!           <quote><acronym>HTML</acronym></quote> and
            <quote>LaTeX</quote> modes put out tables that are intended to
            be included in documents using the respective mark-up
            language. They are not complete documents! (This might not be
--- 1526,1535 ----
            is intended to create output that might be intended to be read
            in by other programs (tab-separated, comma-separated).
            <quote>Aligned</quote> mode is the standard, human-readable,
!           nicely formatted text output that is default.
!           <quote>Wrapped</quote> is like <literal>aligned</> but wraps
!           the output to fit the detected screen width or <literal>\pset
!           columns</>.  The <quote><acronym>HTML</acronym></quote> and
            <quote>LaTeX</quote> modes put out tables that are intended to
            be included in documents using the respective mark-up
            language. They are not complete documents! (This might not be
***************
*** 1537,1542 ****
--- 1540,1556 ----
            </varlistentry>

            <varlistentry>
+           <term><literal>columns</literal></term>
+           <listitem>
+           <para>
+           Controls the target width for the <literal>wrap</> format
+           when output is not to the screen, or the operating system does
+           not support screen width detection.
+           </para>
+           </listitem>
+           </varlistentry>
+
+           <varlistentry>
            <term><literal>border</literal></term>
            <listitem>
            <para>
Index: src/bin/psql/command.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v
retrieving revision 1.186
diff -c -c -r1.186 command.c
*** src/bin/psql/command.c    1 Jan 2008 19:45:55 -0000    1.186
--- src/bin/psql/command.c    20 Apr 2008 03:53:39 -0000
***************
*** 1526,1531 ****
--- 1526,1534 ----
          case PRINT_ALIGNED:
              return "aligned";
              break;
+         case PRINT_WRAP:
+             return "wrapped";
+             break;
          case PRINT_HTML:
              return "html";
              break;
***************
*** 1559,1564 ****
--- 1562,1569 ----
              popt->topt.format = PRINT_UNALIGNED;
          else if (pg_strncasecmp("aligned", value, vallen) == 0)
              popt->topt.format = PRINT_ALIGNED;
+         else if (pg_strncasecmp("wrapped", value, vallen) == 0)
+             popt->topt.format = PRINT_WRAP;
          else if (pg_strncasecmp("html", value, vallen) == 0)
              popt->topt.format = PRINT_HTML;
          else if (pg_strncasecmp("latex", value, vallen) == 0)
***************
*** 1567,1573 ****
              popt->topt.format = PRINT_TROFF_MS;
          else
          {
!             psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n");
              return false;
          }

--- 1572,1578 ----
              popt->topt.format = PRINT_TROFF_MS;
          else
          {
!             psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
              return false;
          }

***************
*** 1748,1753 ****
--- 1753,1768 ----
          }
      }

+     /* set border style/width */
+     else if (strcmp(param, "columns") == 0)
+     {
+         if (value)
+             popt->topt.columns = atoi(value);
+
+         if (!quiet)
+             printf(_("Target column width for \"wrap\" format is %d.\n"), popt->topt.columns);
+     }
+
      else
      {
          psql_error("\\pset: unknown option: %s\n", param);
Index: src/bin/psql/mbprint.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/mbprint.c,v
retrieving revision 1.30
diff -c -c -r1.30 mbprint.c
*** src/bin/psql/mbprint.c    16 Apr 2008 18:18:00 -0000    1.30
--- src/bin/psql/mbprint.c    20 Apr 2008 03:53:41 -0000
***************
*** 204,210 ****
  /*
   * pg_wcssize takes the given string in the given encoding and returns three
   * values:
!  *      result_width: Width in display character of longest line in string
   *      result_height: Number of lines in display output
   *      result_format_size: Number of bytes required to store formatted representation of string
   */
--- 204,210 ----
  /*
   * pg_wcssize takes the given string in the given encoding and returns three
   * values:
!  *      result_width: Width in display characters of the longest line in string
   *      result_height: Number of lines in display output
   *      result_format_size: Number of bytes required to store formatted representation of string
   */
***************
*** 279,284 ****
--- 279,288 ----
      return width;
  }

+ /*
+  *  Filter out unprintable characters, companion to wcs_size.
+  *  Break input into lines (based on \n or \r).
+  */
  void
  pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
               struct lineptr * lines, int count)
***************
*** 353,364 ****
          }
          len -= chlen;
      }
!     *ptr++ = '\0';
      lines->width = linewidth;
!     lines++;
!     count--;
!     if (count > 0)
          lines->ptr = NULL;
  }

  unsigned char *
--- 357,373 ----
          }
          len -= chlen;
      }
!     *ptr++ = '\0';            /* Terminate formatted string */
!
      lines->width = linewidth;
!
!     /* Fill remaining array slots with nulls */
!     while (--count)
!     {
!         lines++;
          lines->ptr = NULL;
+         lines->width = 0;
+     }
  }

  unsigned char *
Index: src/bin/psql/print.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v
retrieving revision 1.97
diff -c -c -r1.97 print.c
*** src/bin/psql/print.c    27 Mar 2008 03:57:34 -0000    1.97
--- src/bin/psql/print.c    20 Apr 2008 03:53:42 -0000
***************
*** 28,33 ****
--- 28,35 ----

  #include "mbprint.h"

+ static int mb_strlen_max_width(char *str, int *width, int encoding);
+
  /*
   * We define the cancel_pressed flag in this file, rather than common.c where
   * it naturally belongs, because this file is also used by non-psql programs
***************
*** 43,48 ****
--- 45,51 ----
  static char *grouping;
  static char *thousands_sep;

+
  static void *
  pg_local_malloc(size_t size)
  {
***************
*** 396,401 ****
--- 399,407 ----
  }


+ /*
+  *    Prety pretty boxes around cells.
+  */
  static void
  print_aligned_text(const char *title, const char *const * headers,
                     const char *const * cells, const char *const * footers,
***************
*** 404,429 ****
  {
      bool        opt_tuples_only = opt->tuples_only;
      bool        opt_numeric_locale = opt->numericLocale;
-     unsigned short int opt_border = opt->border;
      int            encoding = opt->encoding;
!     unsigned int col_count = 0;
!     unsigned int cell_count = 0;
!     unsigned int i;
!     int            tmp;
!     unsigned int *widths,
!                 total_w;
!     unsigned int *heights;
!     unsigned int *format_space;
      unsigned char **format_buf;

      const char *const * ptr;

!     struct lineptr **col_lineptrs;        /* pointers to line pointer for each
!                                          * column */
      struct lineptr *lineptr_list;        /* complete list of linepointers */

!     int           *complete;        /* Array remembering which columns have
!                                  * completed output */

      if (cancel_pressed)
          return;
--- 410,441 ----
  {
      bool        opt_tuples_only = opt->tuples_only;
      bool        opt_numeric_locale = opt->numericLocale;
      int            encoding = opt->encoding;
!     unsigned short int opt_border = opt->border;
!
!     unsigned int col_count = 0, cell_count = 0;
!
!     unsigned int i,
!                 j;
!
!     unsigned int *width_header,
!                *width_max,
!                *width_wrap,
!                *width_average;
!     unsigned int *heights,
!                *format_space;
      unsigned char **format_buf;
+     unsigned int width_total;
+     unsigned int total_header_width;

      const char *const * ptr;

!     struct lineptr **col_lineptrs;        /* pointers to line pointer per column */
      struct lineptr *lineptr_list;        /* complete list of linepointers */

!     bool       *header_done;    /* Have all header lines been output? */
!     int           *bytes_output;    /* Bytes output for column value */
!     int            tcolumns = 0;    /* Width of interactive console */

      if (cancel_pressed)
          return;
***************
*** 437,533 ****

      if (col_count > 0)
      {
!         widths = pg_local_calloc(col_count, sizeof(*widths));
          heights = pg_local_calloc(col_count, sizeof(*heights));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
          format_space = pg_local_calloc(col_count, sizeof(*format_space));
          format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
!         complete = pg_local_calloc(col_count, sizeof(*complete));
      }
      else
      {
!         widths = NULL;
          heights = NULL;
          col_lineptrs = NULL;
          format_space = NULL;
          format_buf = NULL;
!         complete = NULL;
      }

!     /* count cells (rows * cols) */
!     for (ptr = cells; *ptr; ptr++)
!         cell_count++;
!
!     /* calc column widths */
      for (i = 0; i < col_count; i++)
      {
          /* Get width & height */
!         int            height,
                      space;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space);
!         if (tmp > widths[i])
!             widths[i] = tmp;
          if (height > heights[i])
              heights[i] = height;
          if (space > format_space[i])
              format_space[i] = space;
      }

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

-         if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
-             numeric_locale_len = additional_numeric_locale_len(*ptr);
-         else
-             numeric_locale_len = 0;
-
          /* Get width, ignore height */
!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space);
!         tmp += numeric_locale_len;
!         if (tmp > widths[i % col_count])
!             widths[i % col_count] = tmp;
          if (height > heights[i % col_count])
              heights[i % col_count] = height;
          if (space > format_space[i % col_count])
              format_space[i % col_count] = space;
      }

      if (opt_border == 0)
!         total_w = col_count - 1;
      else if (opt_border == 1)
!         total_w = col_count * 3 - 1;
      else
!         total_w = col_count * 3 + 1;

      for (i = 0; i < col_count; i++)
!         total_w += widths[i];

      /*
!      * At this point: widths contains the max width of each column heights
!      * contains the max height of a cell of each column format_space contains
!      * maximum space required to store formatted string so we prepare the
!      * formatting structures
       */
      if (col_count > 0)
      {
!         int            heights_total = 0;
          struct lineptr *lineptr;

          for (i = 0; i < col_count; i++)
!             heights_total += heights[i];

!         lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list));

          for (i = 0; i < col_count; i++)
          {
              col_lineptrs[i] = lineptr;
              lineptr += heights[i];

!             format_buf[i] = pg_local_malloc(format_space[i]);

              col_lineptrs[i]->ptr = format_buf[i];
          }
--- 449,570 ----

      if (col_count > 0)
      {
!         width_header = pg_local_calloc(col_count, sizeof(*width_header));
!         width_average = pg_local_calloc(col_count, sizeof(*width_average));
!         width_max = pg_local_calloc(col_count, sizeof(*width_max));
!         width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap));
          heights = pg_local_calloc(col_count, sizeof(*heights));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
          format_space = pg_local_calloc(col_count, sizeof(*format_space));
          format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
!         header_done = pg_local_calloc(col_count, sizeof(*header_done));
!         bytes_output = pg_local_calloc(col_count, sizeof(*bytes_output));
      }
      else
      {
!         width_header = NULL;
!         width_average = NULL;
!         width_max = NULL;
!         width_wrap = NULL;
          heights = NULL;
          col_lineptrs = NULL;
          format_space = NULL;
          format_buf = NULL;
!         header_done = NULL;
!         bytes_output = NULL;
      }

!     /* scan all column headers, find maximum width */
      for (i = 0; i < col_count; i++)
      {
          /* Get width & height */
!         int            width,
!                     height,
                      space;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &space);
!         if (width > width_max[i])
!             width_max[i] = width;
          if (height > heights[i])
              heights[i] = height;
          if (space > format_space[i])
              format_space[i] = space;
+
+         width_header[i] = width;
      }

!     /* scan all rows, find maximum width, compute cell_count */
!     for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++)
      {
!         int            width,
!                     height,
                      space;

          /* Get width, ignore height */
!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &space);
!         if (opt_numeric_locale && opt_align[i % col_count] == 'r')
!         {
!             width += additional_numeric_locale_len(*ptr);
!             space += additional_numeric_locale_len(*ptr);
!         }
!
!         if (width > width_max[i % col_count])
!             width_max[i % col_count] = width;
          if (height > heights[i % col_count])
              heights[i % col_count] = height;
          if (space > format_space[i % col_count])
              format_space[i % col_count] = space;
+
+         width_average[i % col_count] += width;
+     }
+
+     /* If we have rows, compute average */
+     if (col_count != 0 && cell_count != 0)
+     {
+         int rows = cell_count / col_count;
+
+         for (i = 0; i < col_count; i++)
+             width_average[i % col_count] /= rows;
      }

+     /* adjust the total display width based on border style */
      if (opt_border == 0)
!         width_total = col_count - 1;
      else if (opt_border == 1)
!         width_total = col_count * 3 - 1;
      else
!         width_total = col_count * 3 + 1;
!     total_header_width = width_total;

      for (i = 0; i < col_count; i++)
!     {
!         width_total += width_max[i];
!         total_header_width += width_header[i];
!     }

      /*
!      * At this point: width_max[] contains the max width of each column,
!      * heights[] contains the max number of lines in each column,
!      * format_space[] contains the maximum storage space for formatting
!      * strings, width_total contains the giant width sum.  Now we allocate
!      * some memory...
       */
      if (col_count > 0)
      {
!         int            height_total = 0;
          struct lineptr *lineptr;

          for (i = 0; i < col_count; i++)
!             height_total += heights[i];

!         lineptr = lineptr_list = pg_local_calloc(height_total, sizeof(*lineptr_list));

          for (i = 0; i < col_count; i++)
          {
              col_lineptrs[i] = lineptr;
              lineptr += heights[i];

!             format_buf[i] = pg_local_malloc(format_space[i] + 1);

              col_lineptrs[i]->ptr = format_buf[i];
          }
***************
*** 535,571 ****
      else
          lineptr_list = NULL;

      if (opt->start_table)
      {
          /* print title */
          if (title && !opt_tuples_only)
          {
!             /* Get width & height */
!             int            height;

!             pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL);
!             if (tmp >= total_w)
!                 fprintf(fout, "%s\n", title);
              else
!                 fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title);
          }

          /* print headers */
          if (!opt_tuples_only)
          {
!             int            cols_todo;
              int            line_count;

              if (opt_border == 2)
!                 _print_horizontal_line(col_count, widths, opt_border, fout);

              for (i = 0; i < col_count; i++)
                  pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i],
heights[i]);

!             cols_todo = col_count;
              line_count = 0;
!             memset(complete, 0, col_count * sizeof(int));
!             while (cols_todo)
              {
                  if (opt_border == 2)
                      fprintf(fout, "|%c", line_count ? '+' : ' ');
--- 572,678 ----
      else
          lineptr_list = NULL;

+
+     /* Default word wrap to the full width, i.e. no word wrap */
+     for (i = 0; i < col_count; i++)
+         width_wrap[i] = width_max[i];
+
+     /*
+      * Optional optimized word wrap. Shrink columns with a high max/avg ratio.
+      * Slighly bias against wider columns (increases chance a narrow column
+      * will fit in its cell)
+      */
+     if (opt->format == PRINT_WRAP)
+     {
+         /* Get terminal width */
+ #ifdef TIOCGWINSZ
+         if (fout == stdout && isatty(fileno(stdout)))
+         {
+             struct winsize screen_size;
+
+             if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
+                 tcolumns = screen_size.ws_col;
+         }
+         else
+ #endif
+             tcolumns = opt->columns;
+
+         /*
+          * If available columns is positive...
+          * and greater than the width of the unshrinkable column headers
+          */
+         if (tcolumns > 0 && tcolumns >= total_header_width)
+         {
+             /* While there is still excess width... */
+             while (width_total > tcolumns)
+             {
+                 double        max_ratio = 0;
+                 int            worst_col = -1;
+
+                 /*
+                  *    Find column that has the highest ratio of its maximum
+                  *    width compared to its average width.  This tells us which
+                  *    column will produce the fewest wrapped values if shortened.
+                  *    width_wrap starts as equal to width_max.
+                  */
+                 for (i = 0; i < col_count; i++)
+                 {
+                     if (width_average[i] && width_wrap[i] > width_header[i])
+                     {
+                         /* Penalize wide columns by +1% of their width (0.01) */
+                         double ratio = (double)width_wrap[i] / width_average[i] +
+                                     width_max[i] * 0.01;
+
+                         if (ratio > max_ratio)
+                         {
+                             max_ratio = ratio;
+                             worst_col = i;
+                         }
+                     }
+                 }
+
+                 /* Exit loop if we can't squeeze any more. */
+                 if (worst_col == -1)
+                     break;
+
+                 width_wrap[worst_col]--;
+                 width_total--;
+             }
+         }
+     }
+
+     /* time to output */
      if (opt->start_table)
      {
          /* print title */
          if (title && !opt_tuples_only)
          {
!             int            width,
!                         height;

!             pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL);
!             if (width >= width_total)
!                 fprintf(fout, "%s\n", title);    /* Aligned */
              else
!                 fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title);        /* Centered */
          }

          /* print headers */
          if (!opt_tuples_only)
          {
!             int            more_col_wrapping;
              int            line_count;

              if (opt_border == 2)
!                 _print_horizontal_line(col_count, width_wrap, opt_border, fout);

              for (i = 0; i < col_count; i++)
                  pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i],
heights[i]);

!             more_col_wrapping = col_count;
              line_count = 0;
!             memset(header_done, false, col_count * sizeof(bool));
!             while (more_col_wrapping)
              {
                  if (opt_border == 2)
                      fprintf(fout, "|%c", line_count ? '+' : ' ');
***************
*** 578,586 ****

                      struct lineptr *this_line = col_lineptrs[i] + line_count;

!                     if (!complete[i])
                      {
!                         nbspace = widths[i] - this_line->width;

                          /* centered */
                          fprintf(fout, "%-*s%s%-*s",
--- 685,693 ----

                      struct lineptr *this_line = col_lineptrs[i] + line_count;

!                     if (!header_done[i])
                      {
!                         nbspace = width_wrap[i] - this_line->width;

                          /* centered */
                          fprintf(fout, "%-*s%s%-*s",
***************
*** 588,599 ****

                          if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr)
                          {
!                             cols_todo--;
!                             complete[i] = 1;
                          }
                      }
                      else
!                         fprintf(fout, "%*s", widths[i], "");
                      if (i < col_count - 1)
                      {
                          if (opt_border == 0)
--- 695,706 ----

                          if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr)
                          {
!                             more_col_wrapping--;
!                             header_done[i] = 1;
                          }
                      }
                      else
!                         fprintf(fout, "%*s", width_wrap[i], "");
                      if (i < col_count - 1)
                      {
                          if (opt_border == 0)
***************
*** 611,711 ****
                  fputc('\n', fout);
              }

!             _print_horizontal_line(col_count, widths, opt_border, fout);
          }
      }

      /* print cells */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         int            j;
!         int            cols_todo = col_count;
!         int            line_count; /* Number of lines output so far in row */

          if (cancel_pressed)
              break;

          for (j = 0; j < col_count; j++)
              pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);

!         line_count = 0;
!         memset(complete, 0, col_count * sizeof(int));
!         while (cols_todo)
!         {
!             /* beginning of line */
!             if (opt_border == 2)
!                 fputs("| ", fout);
!             else if (opt_border == 1)
!                 fputc(' ', fout);

!             for (j = 0; j < col_count; j++)
              {
!                 struct lineptr *this_line = col_lineptrs[j] + line_count;
!                 bool        finalspaces = (opt_border == 2 || j != col_count - 1);

!                 if (complete[j])    /* Just print spaces... */
!                 {
!                     if (finalspaces)
!                         fprintf(fout, "%*s", widths[j], "");
!                 }
!                 else
                  {
!                     /* content */
!                     if (opt_align[j] == 'r')
                      {
!                         if (opt_numeric_locale)
                          {
!                             /*
!                              * Assumption: This code used only on strings
!                              * without multibyte characters, otherwise
!                              * this_line->width < strlen(this_ptr) and we get
!                              * an overflow
!                              */
!                             char       *my_cell = format_numeric_locale((char *) this_line->ptr);
!
!                             fprintf(fout, "%*s%s",
!                                     (int) (widths[i % col_count] - strlen(my_cell)), "",
!                                     my_cell);
!                             free(my_cell);
                          }
-                         else
-                             fprintf(fout, "%*s%s",
-                                     widths[j] - this_line->width, "",
-                                     this_line->ptr);
                      }
!                     else
!                         fprintf(fout, "%-s%*s", this_line->ptr,
!                         finalspaces ? (widths[j] - this_line->width) : 0, "");
!                     /* If at the right height, done this col */
!                     if (line_count == heights[j] - 1 || !this_line[1].ptr)
                      {
!                         complete[j] = 1;
!                         cols_todo--;
                      }
                  }

!                 /* divider */
!                 if ((j + 1) % col_count)
                  {
!                     if (opt_border == 0)
!                         fputc(' ', fout);
!                     else if (line_count == 0)
!                         fputs(" | ", fout);
!                     else
!                         fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':');
                  }
              }
!             if (opt_border == 2)
!                 fputs(" |", fout);
!             fputc('\n', fout);
!             line_count++;
!         }
      }

      if (opt->stop_table)
      {
          if (opt_border == 2 && !cancel_pressed)
!             _print_horizontal_line(col_count, widths, opt_border, fout);

          /* print footers */
          if (footers && !opt_tuples_only && !cancel_pressed)
--- 718,877 ----
                  fputc('\n', fout);
              }

!             _print_horizontal_line(col_count, width_wrap, opt_border, fout);
          }
      }

      /* print cells */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         bool        more_newline_values,
!                     more_col_wrapping;
!         int            line_count = 0, col_line_count;

          if (cancel_pressed)
              break;

+         /*
+          * Format each cell.  Format again, it is a numeric formatting locale
+          * (e.g. 123,456 vs. 123456)
+          */
          for (j = 0; j < col_count; j++)
+         {
              pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);
+             if (opt_numeric_locale && opt_align[j % col_count] == 'r')
+             {
+                 char       *my_cell;

!                 my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr);
!                 strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large
!                                                                  * enough... now */
!                 free(my_cell);
!             }
!         }

!         /* Print rows to console */
!         memset(bytes_output, 0, col_count * sizeof(int));
!
!         do
!         {
!             col_line_count = 0;
!             do
              {
!                 /* left border */
!                 if (opt_border == 2)
!                     fputs("| ", fout);
!                 else if (opt_border == 1)
!                     fputc(' ', fout);

!                 more_col_wrapping = false;
!
!                 /* for each column */
!                 for (j = 0; j < col_count; j++)
                  {
!                     /* Past column height so pad for other columns */
!                     if (line_count >= heights[j])
!                     {
!                         fprintf(fout, "%*s", width_wrap[j], "");
!                     }
!                     else
                      {
!                         struct lineptr *this_line = &col_lineptrs[j][line_count];
!                         int        bytes_to_output,  chars_to_output = width_wrap[j];
!
!                         /* Get strlen() of the width_wrap character */
!                         bytes_to_output = mb_strlen_max_width(this_line->ptr +
!                                         bytes_output[j], &chars_to_output, encoding);
!
!                         /* value fills column? */
!                         if (chars_to_output == width_wrap[j])
!                         {
!                             /* If we are filling the column, alignment doesn't matter */
!
!                             fprintf(fout, "%.*s", (int) bytes_to_output,
!                                     this_line->ptr + bytes_output[j]);
!                             bytes_output[j] += bytes_to_output;
!                             /* Is the string done? */
!                             more_col_wrapping = (*(this_line->ptr + bytes_output[j]) != 0);
!                         }
!                         else    /* last line of column value */
                          {
!                             if (opt_align[j] == 'r')        /* Right aligned cell */
!                             {
!                                 /* spaces first */
!                                 fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
!                                 fprintf(fout, "%-s", this_line->ptr + bytes_output[j]);
!                             }
!                             else    /* Left aligned cell */
!                             {
!                                 /* spaces second */
!                                 fprintf(fout, "%-s", this_line->ptr + bytes_output[j]);
!                                 fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
!                             }
!                             bytes_output[j] += bytes_to_output;
                          }
                      }
!
!                     /* print a divider, middle columns only */
!                     if ((j + 1) % col_count)
                      {
!                         if (opt_border == 0)
!                             fputc(' ', fout);
!                         /* first line for values? */
!                         else if (line_count == 0 && col_line_count == 0)
!                             fputs(" | ", fout);
!                         /* next value is beyond height? */
!                         else if (line_count >= heights[j + 1])
!                             fputs("   ", fout);
!                         /* start of another newline string? */
!                         else if (col_line_count == 0)
!                             fputs(" : ", fout);
!                         else
!                         {
!                             /* does the next column wrap to this line? */
!                             struct lineptr *this_line = &col_lineptrs[j+1][line_count];
!                             bool    string_done = *(this_line->ptr + bytes_output[j+1]) == 0;
!
!                             fputs(string_done ? "   " : " ; ", fout);
!                         }
                      }
                  }

!                 /* end of row border */
!                 if (opt_border == 2)
!                     fputs(" | ", fout);
!                 fputc('\n', fout);
!                 col_line_count++;
!
!             } while (more_col_wrapping);
!
!             /*
!              * Check if any columns have line continuations due to \n in the
!              * cell.
!              */
!             line_count++;
!
!             /* Do we have more lines for any column value, i.e embedded \n? */
!             more_newline_values = false;
!             for (j = 0; j < col_count; j++)
!             {
!                 if (line_count < heights[j])
                  {
!                     if (col_lineptrs[j][line_count].ptr)
!                     {
!                         more_newline_values = true;
!                         bytes_output[j] = 0;
!                     }
                  }
              }
!
!         } while (more_newline_values);
      }

      if (opt->stop_table)
      {
          if (opt_border == 2 && !cancel_pressed)
!             _print_horizontal_line(col_count, width_wrap, opt_border, fout);

          /* print footers */
          if (footers && !opt_tuples_only && !cancel_pressed)
***************
*** 722,732 ****
      }

      /* clean up */
!     free(widths);
      free(heights);
      free(col_lineptrs);
      free(format_space);
!     free(complete);
      free(lineptr_list);
      for (i = 0; i < col_count; i++)
          free(format_buf[i]);
--- 888,902 ----
      }

      /* clean up */
!     free(width_header);
!     free(width_average);
!     free(width_max);
!     free(width_wrap);
      free(heights);
      free(col_lineptrs);
      free(format_space);
!     free(header_done);
!     free(bytes_output);
      free(lineptr_list);
      for (i = 0; i < col_count; i++)
          free(format_buf[i]);
***************
*** 754,760 ****
                  dheight = 1,
                  hformatsize = 0,
                  dformatsize = 0;
-     int            tmp = 0;
      char       *divider;
      unsigned int cell_count = 0;
      struct lineptr *hlineptr,
--- 924,929 ----
***************
*** 779,790 ****
      /* Find the maximum dimensions for the headers */
      for (i = 0; i < col_count; i++)
      {
!         int            height,
                      fs;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs);
!         if (tmp > hwidth)
!             hwidth = tmp;
          if (height > hheight)
              hheight = height;
          if (fs > hformatsize)
--- 948,960 ----
      /* Find the maximum dimensions for the headers */
      for (i = 0; i < col_count; i++)
      {
!         int            width,
!                     height,
                      fs;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs);
!         if (width > hwidth)
!             hwidth = width;
          if (height > hheight)
              hheight = height;
          if (fs > hformatsize)
***************
*** 799,805 ****
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
          int            numeric_locale_len;
!         int            height,
                      fs;

          if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
--- 969,976 ----
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
          int            numeric_locale_len;
!         int            width,
!                     height,
                      fs;

          if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
***************
*** 807,816 ****
          else
              numeric_locale_len = 0;

!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &fs);
!         tmp += numeric_locale_len;
!         if (tmp > dwidth)
!             dwidth = tmp;
          if (height > dheight)
              dheight = height;
          if (fs > dformatsize)
--- 978,987 ----
          else
              numeric_locale_len = 0;

!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs);
!         width += numeric_locale_len;
!         if (width > dwidth)
!             dwidth = width;
          if (height > dheight)
              dheight = height;
          if (fs > dformatsize)
***************
*** 1879,1884 ****
--- 2050,2056 ----
                                       opt, output);
              break;
          case PRINT_ALIGNED:
+         case PRINT_WRAP:
              if (opt->expanded)
                  print_aligned_vertical(title, headers, cells, footers, align,
                                         opt, output);
***************
*** 2066,2068 ****
--- 2238,2262 ----
      else
          thousands_sep = ".";
  }
+
+ /*
+  *    Returns the byte length to the end of the specified character
+  *  and number of display characters processed (useful if the string
+  *    is shorter then dpylen).
+  */
+ static int
+ mb_strlen_max_width(char *str, int *width, int encoding)
+ {
+     int width_left = *width;
+     char *start = str;
+
+     *width = 0;
+     while (*str && width_left--)
+     {
+         str += PQmblen(str, encoding);
+         (*width)++;
+     }
+
+     /* last byte */
+     return str - start;
+ }
Index: src/bin/psql/print.h
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v
retrieving revision 1.35
diff -c -c -r1.35 print.h
*** src/bin/psql/print.h    1 Jan 2008 19:45:56 -0000    1.35
--- src/bin/psql/print.h    20 Apr 2008 03:53:42 -0000
***************
*** 21,26 ****
--- 21,27 ----
      PRINT_NOTHING = 0,            /* to make sure someone initializes this */
      PRINT_UNALIGNED,
      PRINT_ALIGNED,
+     PRINT_WRAP,
      PRINT_HTML,
      PRINT_LATEX,
      PRINT_TROFF_MS
***************
*** 47,52 ****
--- 48,54 ----
                                   * decimal marker */
      char       *tableAttr;        /* attributes for HTML <table ...> */
      int            encoding;        /* character encoding */
+     int            columns;        /* target width for wrap format */
  } printTableOpt;



Re: Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:
The newline handling code was, by far, the most complex part of this
patch.  While I think it would be nicer to "filter up" past the newline,
I just don't see this as a common need.  Large text fields with newlines
are difficult to really view in psql anyway, and they are likely to be
the longest columns in any given query.   Bottom line: not worth messing
with.

I'm split on the new formatting style.  It looks a lot less "grid-like",
which I might like once I get used to it (which will be a while, because
I can't run my own patch in daily use, as my servers are too old).  I
use a version of the wrapping patch that I backported to postgres 8.1,
which was prior to multibyte support, and much much simpler.

I got this warning compiling:
print.c:784: warning: pointer targets in passing argument 1 of
‘mb_strlen_max_width’ differ in signedness
And I did have trouble applying the patch -- I had to manually give it
the filename, and tell it to reverse the patch.


Re: Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:
1) "\pset columns XX" should make it clear that's for file output only.

2) There's an extra space, which breaks \pset border 2

717c717
<                                       fputc(' ', fout);;
---
 >                                       fputc(' ', fout);
842c842
<                                       fputs(" | ", fout);
---
 >                                       fputs(" |", f

2) With \pset border 2, the far left border, for symmetry, should work
like the middle borders.

3) I'm getting bolder: how about having \pset format wrapped as the
default?  Any downsides?




Re: Coding standards

From
Peter Eisentraut
Date:
Alvaro Herrera wrote:
> The developer's FAQ is supposed to contain this kind of thing, but I
> think it's rather thin on actual details.  (Some time ago it was
> proposed to create a "style guide", but people here thought it was a
> waste of time and "it will not cover what's really important", so we're
> stuck with repeating the advice over and over.)

Excellent wiki material ...

Re: Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
Bryce Nesbitt wrote:
> The newline handling code was, by far, the most complex part of this
> patch.  While I think it would be nicer to "filter up" past the newline,
> I just don't see this as a common need.  Large text fields with newlines
> are difficult to really view in psql anyway, and they are likely to be
> the longest columns in any given query.   Bottom line: not worth messing
> with.

OK, we can always return to it.

> I'm split on the new formatting style.  It looks a lot less "grid-like",
> which I might like once I get used to it (which will be a while, because
> I can't run my own patch in daily use, as my servers are too old).  I
> use a version of the wrapping patch that I backported to postgres 8.1,
> which was prior to multibyte support, and much much simpler.

What "new formatting style" are you referring to above? Did I modify
what you sent as the original patch?

> I got this warning compiling:
> print.c:784: warning: pointer targets in passing argument 1 of
> ?mb_strlen_max_width? differ in signedness
> And I did have trouble applying the patch -- I had to manually give it
> the filename, and tell it to reverse the patch.

OK, fixed.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +

Re: Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
Bryce Nesbitt wrote:
> 1) "\pset columns XX" should make it clear that's for file output only.

OK, docs updated.

> 2) There's an extra space, which breaks \pset border 2
>
> 717c717
> <                                       fputc(' ', fout);;
> ---
>  >                                       fputc(' ', fout);
> 842c842
> <                                       fputs(" | ", fout);
> ---
>  >                                       fputs(" |", f

OK, got them fixed.

> 2) With \pset border 2, the far left border, for symmetry, should work
> like the middle borders.

OK, how does it look now with this patch?

> 3) I'm getting bolder: how about having \pset format wrapped as the
> default?  Any downsides?

I think we are going to want it as the default for many psql
informational commands, like \df.  Not sure about a more general
default.  We were just discussing using \x as a default for wide output
but it seems this wrap style is probably a better solution than
switching for \x for wide columns (less distracting for the user and
cleaner).  That will have to be a separate discussion once we are done.

Oh, I found a problem in my coding of the new wrap function I added.
While I handled the case where a character might span multiple bytes, I
assumed all characters had a display width of one.  You can see from
pg_wcsformat()'s use of PQdsplen() that this isn't always the case. I
have modified the patch to properly use PQdsplen() but we are going to
need multi-byte users to test this once we are done.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +
Index: doc/src/sgml/ref/psql-ref.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v
retrieving revision 1.199
diff -c -c -r1.199 psql-ref.sgml
*** doc/src/sgml/ref/psql-ref.sgml    30 Mar 2008 18:10:20 -0000    1.199
--- doc/src/sgml/ref/psql-ref.sgml    21 Apr 2008 15:27:55 -0000
***************
*** 1513,1519 ****
            <listitem>
            <para>
            Sets the output format to one of <literal>unaligned</literal>,
!           <literal>aligned</literal>, <literal>html</literal>,
            <literal>latex</literal>, or <literal>troff-ms</literal>.
            Unique abbreviations are allowed.  (That would mean one letter
            is enough.)
--- 1513,1520 ----
            <listitem>
            <para>
            Sets the output format to one of <literal>unaligned</literal>,
!           <literal>aligned</literal>, <literal>wrapped</literal>,
!           <literal>html</literal>,
            <literal>latex</literal>, or <literal>troff-ms</literal>.
            Unique abbreviations are allowed.  (That would mean one letter
            is enough.)
***************
*** 1525,1532 ****
            is intended to create output that might be intended to be read
            in by other programs (tab-separated, comma-separated).
            <quote>Aligned</quote> mode is the standard, human-readable,
!           nicely formatted text output that is default. The
!           <quote><acronym>HTML</acronym></quote> and
            <quote>LaTeX</quote> modes put out tables that are intended to
            be included in documents using the respective mark-up
            language. They are not complete documents! (This might not be
--- 1526,1535 ----
            is intended to create output that might be intended to be read
            in by other programs (tab-separated, comma-separated).
            <quote>Aligned</quote> mode is the standard, human-readable,
!           nicely formatted text output that is default.
!           <quote>Wrapped</quote> is like <literal>aligned</> but wraps
!           the output to fit the detected screen width or <literal>\pset
!           columns</>.  The <quote><acronym>HTML</acronym></quote> and
            <quote>LaTeX</quote> modes put out tables that are intended to
            be included in documents using the respective mark-up
            language. They are not complete documents! (This might not be
***************
*** 1537,1542 ****
--- 1540,1556 ----
            </varlistentry>

            <varlistentry>
+           <term><literal>columns</literal></term>
+           <listitem>
+           <para>
+           Controls the target width for the <literal>wrap</> format when
+           output is to a file, or the operating system does not support
+           screen width detection.
+           </para>
+           </listitem>
+           </varlistentry>
+
+           <varlistentry>
            <term><literal>border</literal></term>
            <listitem>
            <para>
Index: src/bin/psql/command.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v
retrieving revision 1.186
diff -c -c -r1.186 command.c
*** src/bin/psql/command.c    1 Jan 2008 19:45:55 -0000    1.186
--- src/bin/psql/command.c    21 Apr 2008 15:27:56 -0000
***************
*** 1526,1531 ****
--- 1526,1534 ----
          case PRINT_ALIGNED:
              return "aligned";
              break;
+         case PRINT_WRAP:
+             return "wrapped";
+             break;
          case PRINT_HTML:
              return "html";
              break;
***************
*** 1559,1564 ****
--- 1562,1569 ----
              popt->topt.format = PRINT_UNALIGNED;
          else if (pg_strncasecmp("aligned", value, vallen) == 0)
              popt->topt.format = PRINT_ALIGNED;
+         else if (pg_strncasecmp("wrapped", value, vallen) == 0)
+             popt->topt.format = PRINT_WRAP;
          else if (pg_strncasecmp("html", value, vallen) == 0)
              popt->topt.format = PRINT_HTML;
          else if (pg_strncasecmp("latex", value, vallen) == 0)
***************
*** 1567,1573 ****
              popt->topt.format = PRINT_TROFF_MS;
          else
          {
!             psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n");
              return false;
          }

--- 1572,1578 ----
              popt->topt.format = PRINT_TROFF_MS;
          else
          {
!             psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
              return false;
          }

***************
*** 1748,1753 ****
--- 1753,1768 ----
          }
      }

+     /* set border style/width */
+     else if (strcmp(param, "columns") == 0)
+     {
+         if (value)
+             popt->topt.columns = atoi(value);
+
+         if (!quiet)
+             printf(_("Target column width for \"wrap\" format is %d.\n"), popt->topt.columns);
+     }
+
      else
      {
          psql_error("\\pset: unknown option: %s\n", param);
Index: src/bin/psql/mbprint.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/mbprint.c,v
retrieving revision 1.30
diff -c -c -r1.30 mbprint.c
*** src/bin/psql/mbprint.c    16 Apr 2008 18:18:00 -0000    1.30
--- src/bin/psql/mbprint.c    21 Apr 2008 15:28:01 -0000
***************
*** 204,210 ****
  /*
   * pg_wcssize takes the given string in the given encoding and returns three
   * values:
!  *      result_width: Width in display character of longest line in string
   *      result_height: Number of lines in display output
   *      result_format_size: Number of bytes required to store formatted representation of string
   */
--- 204,210 ----
  /*
   * pg_wcssize takes the given string in the given encoding and returns three
   * values:
!  *      result_width: Width in display characters of the longest line in string
   *      result_height: Number of lines in display output
   *      result_format_size: Number of bytes required to store formatted representation of string
   */
***************
*** 279,284 ****
--- 279,288 ----
      return width;
  }

+ /*
+  *  Filter out unprintable characters, companion to wcs_size.
+  *  Break input into lines (based on \n or \r).
+  */
  void
  pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
               struct lineptr * lines, int count)
***************
*** 353,364 ****
          }
          len -= chlen;
      }
!     *ptr++ = '\0';
      lines->width = linewidth;
!     lines++;
!     count--;
!     if (count > 0)
          lines->ptr = NULL;
  }

  unsigned char *
--- 357,373 ----
          }
          len -= chlen;
      }
!     *ptr++ = '\0';            /* Terminate formatted string */
!
      lines->width = linewidth;
!
!     /* Fill remaining array slots with nulls */
!     while (--count)
!     {
!         lines++;
          lines->ptr = NULL;
+         lines->width = 0;
+     }
  }

  unsigned char *
Index: src/bin/psql/print.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v
retrieving revision 1.97
diff -c -c -r1.97 print.c
*** src/bin/psql/print.c    27 Mar 2008 03:57:34 -0000    1.97
--- src/bin/psql/print.c    21 Apr 2008 15:28:02 -0000
***************
*** 28,33 ****
--- 28,35 ----

  #include "mbprint.h"

+ static int strlen_max_width(unsigned char *str, int *target_width, int encoding);
+
  /*
   * We define the cancel_pressed flag in this file, rather than common.c where
   * it naturally belongs, because this file is also used by non-psql programs
***************
*** 43,48 ****
--- 45,51 ----
  static char *grouping;
  static char *thousands_sep;

+
  static void *
  pg_local_malloc(size_t size)
  {
***************
*** 396,401 ****
--- 399,407 ----
  }


+ /*
+  *    Prety pretty boxes around cells.
+  */
  static void
  print_aligned_text(const char *title, const char *const * headers,
                     const char *const * cells, const char *const * footers,
***************
*** 404,429 ****
  {
      bool        opt_tuples_only = opt->tuples_only;
      bool        opt_numeric_locale = opt->numericLocale;
-     unsigned short int opt_border = opt->border;
      int            encoding = opt->encoding;
!     unsigned int col_count = 0;
!     unsigned int cell_count = 0;
!     unsigned int i;
!     int            tmp;
!     unsigned int *widths,
!                 total_w;
!     unsigned int *heights;
!     unsigned int *format_space;
      unsigned char **format_buf;

      const char *const * ptr;

!     struct lineptr **col_lineptrs;        /* pointers to line pointer for each
!                                          * column */
      struct lineptr *lineptr_list;        /* complete list of linepointers */

!     int           *complete;        /* Array remembering which columns have
!                                  * completed output */

      if (cancel_pressed)
          return;
--- 410,441 ----
  {
      bool        opt_tuples_only = opt->tuples_only;
      bool        opt_numeric_locale = opt->numericLocale;
      int            encoding = opt->encoding;
!     unsigned short int opt_border = opt->border;
!
!     unsigned int col_count = 0, cell_count = 0;
!
!     unsigned int i,
!                 j;
!
!     unsigned int *width_header,
!                *width_max,
!                *width_wrap,
!                *width_average;
!     unsigned int *heights,
!                *format_space;
      unsigned char **format_buf;
+     unsigned int width_total;
+     unsigned int total_header_width;

      const char *const * ptr;

!     struct lineptr **col_lineptrs;        /* pointers to line pointer per column */
      struct lineptr *lineptr_list;        /* complete list of linepointers */

!     bool       *header_done;    /* Have all header lines been output? */
!     int           *bytes_output;    /* Bytes output for column value */
!     int            tcolumns = 0;    /* Width of interactive console */

      if (cancel_pressed)
          return;
***************
*** 437,533 ****

      if (col_count > 0)
      {
!         widths = pg_local_calloc(col_count, sizeof(*widths));
          heights = pg_local_calloc(col_count, sizeof(*heights));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
          format_space = pg_local_calloc(col_count, sizeof(*format_space));
          format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
!         complete = pg_local_calloc(col_count, sizeof(*complete));
      }
      else
      {
!         widths = NULL;
          heights = NULL;
          col_lineptrs = NULL;
          format_space = NULL;
          format_buf = NULL;
!         complete = NULL;
      }

!     /* count cells (rows * cols) */
!     for (ptr = cells; *ptr; ptr++)
!         cell_count++;
!
!     /* calc column widths */
      for (i = 0; i < col_count; i++)
      {
          /* Get width & height */
!         int            height,
                      space;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space);
!         if (tmp > widths[i])
!             widths[i] = tmp;
          if (height > heights[i])
              heights[i] = height;
          if (space > format_space[i])
              format_space[i] = space;
      }

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

-         if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
-             numeric_locale_len = additional_numeric_locale_len(*ptr);
-         else
-             numeric_locale_len = 0;
-
          /* Get width, ignore height */
!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space);
!         tmp += numeric_locale_len;
!         if (tmp > widths[i % col_count])
!             widths[i % col_count] = tmp;
          if (height > heights[i % col_count])
              heights[i % col_count] = height;
          if (space > format_space[i % col_count])
              format_space[i % col_count] = space;
      }

      if (opt_border == 0)
!         total_w = col_count - 1;
      else if (opt_border == 1)
!         total_w = col_count * 3 - 1;
      else
!         total_w = col_count * 3 + 1;

      for (i = 0; i < col_count; i++)
!         total_w += widths[i];

      /*
!      * At this point: widths contains the max width of each column heights
!      * contains the max height of a cell of each column format_space contains
!      * maximum space required to store formatted string so we prepare the
!      * formatting structures
       */
      if (col_count > 0)
      {
!         int            heights_total = 0;
          struct lineptr *lineptr;

          for (i = 0; i < col_count; i++)
!             heights_total += heights[i];

!         lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list));

          for (i = 0; i < col_count; i++)
          {
              col_lineptrs[i] = lineptr;
              lineptr += heights[i];

!             format_buf[i] = pg_local_malloc(format_space[i]);

              col_lineptrs[i]->ptr = format_buf[i];
          }
--- 449,570 ----

      if (col_count > 0)
      {
!         width_header = pg_local_calloc(col_count, sizeof(*width_header));
!         width_average = pg_local_calloc(col_count, sizeof(*width_average));
!         width_max = pg_local_calloc(col_count, sizeof(*width_max));
!         width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap));
          heights = pg_local_calloc(col_count, sizeof(*heights));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
          format_space = pg_local_calloc(col_count, sizeof(*format_space));
          format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
!         header_done = pg_local_calloc(col_count, sizeof(*header_done));
!         bytes_output = pg_local_calloc(col_count, sizeof(*bytes_output));
      }
      else
      {
!         width_header = NULL;
!         width_average = NULL;
!         width_max = NULL;
!         width_wrap = NULL;
          heights = NULL;
          col_lineptrs = NULL;
          format_space = NULL;
          format_buf = NULL;
!         header_done = NULL;
!         bytes_output = NULL;
      }

!     /* scan all column headers, find maximum width */
      for (i = 0; i < col_count; i++)
      {
          /* Get width & height */
!         int            width,
!                     height,
                      space;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &space);
!         if (width > width_max[i])
!             width_max[i] = width;
          if (height > heights[i])
              heights[i] = height;
          if (space > format_space[i])
              format_space[i] = space;
+
+         width_header[i] = width;
      }

!     /* scan all rows, find maximum width, compute cell_count */
!     for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++)
      {
!         int            width,
!                     height,
                      space;

          /* Get width, ignore height */
!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &space);
!         if (opt_numeric_locale && opt_align[i % col_count] == 'r')
!         {
!             width += additional_numeric_locale_len(*ptr);
!             space += additional_numeric_locale_len(*ptr);
!         }
!
!         if (width > width_max[i % col_count])
!             width_max[i % col_count] = width;
          if (height > heights[i % col_count])
              heights[i % col_count] = height;
          if (space > format_space[i % col_count])
              format_space[i % col_count] = space;
+
+         width_average[i % col_count] += width;
+     }
+
+     /* If we have rows, compute average */
+     if (col_count != 0 && cell_count != 0)
+     {
+         int rows = cell_count / col_count;
+
+         for (i = 0; i < col_count; i++)
+             width_average[i % col_count] /= rows;
      }

+     /* adjust the total display width based on border style */
      if (opt_border == 0)
!         width_total = col_count - 1;
      else if (opt_border == 1)
!         width_total = col_count * 3 - 1;
      else
!         width_total = col_count * 3 + 1;
!     total_header_width = width_total;

      for (i = 0; i < col_count; i++)
!     {
!         width_total += width_max[i];
!         total_header_width += width_header[i];
!     }

      /*
!      * At this point: width_max[] contains the max width of each column,
!      * heights[] contains the max number of lines in each column,
!      * format_space[] contains the maximum storage space for formatting
!      * strings, width_total contains the giant width sum.  Now we allocate
!      * some memory...
       */
      if (col_count > 0)
      {
!         int            height_total = 0;
          struct lineptr *lineptr;

          for (i = 0; i < col_count; i++)
!             height_total += heights[i];

!         lineptr = lineptr_list = pg_local_calloc(height_total, sizeof(*lineptr_list));

          for (i = 0; i < col_count; i++)
          {
              col_lineptrs[i] = lineptr;
              lineptr += heights[i];

!             format_buf[i] = pg_local_malloc(format_space[i] + 1);

              col_lineptrs[i]->ptr = format_buf[i];
          }
***************
*** 535,571 ****
      else
          lineptr_list = NULL;

      if (opt->start_table)
      {
          /* print title */
          if (title && !opt_tuples_only)
          {
!             /* Get width & height */
!             int            height;

!             pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL);
!             if (tmp >= total_w)
!                 fprintf(fout, "%s\n", title);
              else
!                 fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title);
          }

          /* print headers */
          if (!opt_tuples_only)
          {
!             int            cols_todo;
              int            line_count;

              if (opt_border == 2)
!                 _print_horizontal_line(col_count, widths, opt_border, fout);

              for (i = 0; i < col_count; i++)
                  pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i],
heights[i]);

!             cols_todo = col_count;
              line_count = 0;
!             memset(complete, 0, col_count * sizeof(int));
!             while (cols_todo)
              {
                  if (opt_border == 2)
                      fprintf(fout, "|%c", line_count ? '+' : ' ');
--- 572,678 ----
      else
          lineptr_list = NULL;

+
+     /* Default word wrap to the full width, i.e. no word wrap */
+     for (i = 0; i < col_count; i++)
+         width_wrap[i] = width_max[i];
+
+     /*
+      * Optional optimized word wrap. Shrink columns with a high max/avg ratio.
+      * Slighly bias against wider columns (increases chance a narrow column
+      * will fit in its cell)
+      */
+     if (opt->format == PRINT_WRAP)
+     {
+         /* Get terminal width */
+ #ifdef TIOCGWINSZ
+         if (fout == stdout && isatty(fileno(stdout)))
+         {
+             struct winsize screen_size;
+
+             if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
+                 tcolumns = screen_size.ws_col;
+         }
+         else
+ #endif
+             tcolumns = opt->columns;
+
+         /*
+          * If available columns is positive...
+          * and greater than the width of the unshrinkable column headers
+          */
+         if (tcolumns > 0 && tcolumns >= total_header_width)
+         {
+             /* While there is still excess width... */
+             while (width_total > tcolumns)
+             {
+                 double        max_ratio = 0;
+                 int            worst_col = -1;
+
+                 /*
+                  *    Find column that has the highest ratio of its maximum
+                  *    width compared to its average width.  This tells us which
+                  *    column will produce the fewest wrapped values if shortened.
+                  *    width_wrap starts as equal to width_max.
+                  */
+                 for (i = 0; i < col_count; i++)
+                 {
+                     if (width_average[i] && width_wrap[i] > width_header[i])
+                     {
+                         /* Penalize wide columns by +1% of their width (0.01) */
+                         double ratio = (double)width_wrap[i] / width_average[i] +
+                                     width_max[i] * 0.01;
+
+                         if (ratio > max_ratio)
+                         {
+                             max_ratio = ratio;
+                             worst_col = i;
+                         }
+                     }
+                 }
+
+                 /* Exit loop if we can't squeeze any more. */
+                 if (worst_col == -1)
+                     break;
+
+                 width_wrap[worst_col]--;
+                 width_total--;
+             }
+         }
+     }
+
+     /* time to output */
      if (opt->start_table)
      {
          /* print title */
          if (title && !opt_tuples_only)
          {
!             int            width,
!                         height;

!             pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL);
!             if (width >= width_total)
!                 fprintf(fout, "%s\n", title);    /* Aligned */
              else
!                 fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title);        /* Centered */
          }

          /* print headers */
          if (!opt_tuples_only)
          {
!             int            more_col_wrapping;
              int            line_count;

              if (opt_border == 2)
!                 _print_horizontal_line(col_count, width_wrap, opt_border, fout);

              for (i = 0; i < col_count; i++)
                  pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i],
heights[i]);

!             more_col_wrapping = col_count;
              line_count = 0;
!             memset(header_done, false, col_count * sizeof(bool));
!             while (more_col_wrapping)
              {
                  if (opt_border == 2)
                      fprintf(fout, "|%c", line_count ? '+' : ' ');
***************
*** 578,586 ****

                      struct lineptr *this_line = col_lineptrs[i] + line_count;

!                     if (!complete[i])
                      {
!                         nbspace = widths[i] - this_line->width;

                          /* centered */
                          fprintf(fout, "%-*s%s%-*s",
--- 685,693 ----

                      struct lineptr *this_line = col_lineptrs[i] + line_count;

!                     if (!header_done[i])
                      {
!                         nbspace = width_wrap[i] - this_line->width;

                          /* centered */
                          fprintf(fout, "%-*s%s%-*s",
***************
*** 588,599 ****

                          if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr)
                          {
!                             cols_todo--;
!                             complete[i] = 1;
                          }
                      }
                      else
!                         fprintf(fout, "%*s", widths[i], "");
                      if (i < col_count - 1)
                      {
                          if (opt_border == 0)
--- 695,706 ----

                          if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr)
                          {
!                             more_col_wrapping--;
!                             header_done[i] = 1;
                          }
                      }
                      else
!                         fprintf(fout, "%*s", width_wrap[i], "");
                      if (i < col_count - 1)
                      {
                          if (opt_border == 0)
***************
*** 607,711 ****
                  if (opt_border == 2)
                      fputs(" |", fout);
                  else if (opt_border == 1)
!                     fputc(' ', fout);;
                  fputc('\n', fout);
              }

!             _print_horizontal_line(col_count, widths, opt_border, fout);
          }
      }

      /* print cells */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         int            j;
!         int            cols_todo = col_count;
!         int            line_count; /* Number of lines output so far in row */

          if (cancel_pressed)
              break;

          for (j = 0; j < col_count; j++)
              pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);

!         line_count = 0;
!         memset(complete, 0, col_count * sizeof(int));
!         while (cols_todo)
!         {
!             /* beginning of line */
!             if (opt_border == 2)
!                 fputs("| ", fout);
!             else if (opt_border == 1)
!                 fputc(' ', fout);

!             for (j = 0; j < col_count; j++)
              {
!                 struct lineptr *this_line = col_lineptrs[j] + line_count;
!                 bool        finalspaces = (opt_border == 2 || j != col_count - 1);

!                 if (complete[j])    /* Just print spaces... */
!                 {
!                     if (finalspaces)
!                         fprintf(fout, "%*s", widths[j], "");
!                 }
!                 else
                  {
!                     /* content */
!                     if (opt_align[j] == 'r')
                      {
!                         if (opt_numeric_locale)
                          {
!                             /*
!                              * Assumption: This code used only on strings
!                              * without multibyte characters, otherwise
!                              * this_line->width < strlen(this_ptr) and we get
!                              * an overflow
!                              */
!                             char       *my_cell = format_numeric_locale((char *) this_line->ptr);
!
!                             fprintf(fout, "%*s%s",
!                                     (int) (widths[i % col_count] - strlen(my_cell)), "",
!                                     my_cell);
!                             free(my_cell);
                          }
!                         else
!                             fprintf(fout, "%*s%s",
!                                     widths[j] - this_line->width, "",
!                                     this_line->ptr);
                      }
!                     else
!                         fprintf(fout, "%-s%*s", this_line->ptr,
!                         finalspaces ? (widths[j] - this_line->width) : 0, "");
!                     /* If at the right height, done this col */
!                     if (line_count == heights[j] - 1 || !this_line[1].ptr)
                      {
!                         complete[j] = 1;
!                         cols_todo--;
                      }
                  }

!                 /* divider */
!                 if ((j + 1) % col_count)
                  {
!                     if (opt_border == 0)
!                         fputc(' ', fout);
!                     else if (line_count == 0)
!                         fputs(" | ", fout);
!                     else
!                         fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':');
                  }
              }
!             if (opt_border == 2)
!                 fputs(" |", fout);
!             fputc('\n', fout);
!             line_count++;
!         }
      }

      if (opt->stop_table)
      {
          if (opt_border == 2 && !cancel_pressed)
!             _print_horizontal_line(col_count, widths, opt_border, fout);

          /* print footers */
          if (footers && !opt_tuples_only && !cancel_pressed)
--- 714,877 ----
                  if (opt_border == 2)
                      fputs(" |", fout);
                  else if (opt_border == 1)
!                     fputc(' ', fout);
                  fputc('\n', fout);
              }

!             _print_horizontal_line(col_count, width_wrap, opt_border, fout);
          }
      }

      /* print cells */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         bool        more_newline_values,
!                     more_col_wrapping;
!         int            line_count = 0, col_line_count;

          if (cancel_pressed)
              break;

+         /*
+          * Format each cell.  Format again, it is a numeric formatting locale
+          * (e.g. 123,456 vs. 123456)
+          */
          for (j = 0; j < col_count; j++)
+         {
              pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);
+             if (opt_numeric_locale && opt_align[j % col_count] == 'r')
+             {
+                 char       *my_cell;

!                 my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr);
!                 strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large
!                                                                  * enough... now */
!                 free(my_cell);
!             }
!         }

!         /* Print rows to console */
!         memset(bytes_output, 0, col_count * sizeof(int));
!
!         do
!         {
!             col_line_count = 0;
!             do
              {
!                 /* left border */
!                 if (opt_border == 2)
!                     fputs("| ", fout);
!                 else if (opt_border == 1)
!                     fputc(' ', fout);

!                 more_col_wrapping = false;
!
!                 /* for each column */
!                 for (j = 0; j < col_count; j++)
                  {
!                     /* Past column height so pad for other columns */
!                     if (line_count >= heights[j])
!                         fprintf(fout, "%*s", width_wrap[j], "");
!                     else
                      {
!                         struct lineptr *this_line = &col_lineptrs[j][line_count];
!                         int        bytes_to_output,  chars_to_output = width_wrap[j];
!
!                         /* Get strlen() of the width_wrap character */
!                         bytes_to_output = strlen_max_width(this_line->ptr +
!                                         bytes_output[j], &chars_to_output, encoding);
!
!                         /*
!                          *    If we exceeded width_wrap, it means the display width
!                          *    of a single character was wider than our target width.
!                          *    In that case, we have to pretend we are only printing
!                          *    the target display width and make the best of it.
!                          */
!                         if (chars_to_output > width_wrap[j])
!                             chars_to_output = width_wrap[j];
!
!                         if (opt_align[j] == 'r')        /* Right aligned cell */
                          {
!                             /* spaces first */
!                             fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
!                             fprintf(fout, "%.*s", bytes_to_output,
!                                     this_line->ptr + bytes_output[j]);
                          }
!                         else    /* Left aligned cell */
!                         {
!                             /* spaces second */
!                             fprintf(fout, "%.*s", bytes_to_output,
!                                     this_line->ptr + bytes_output[j]);
!                             fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
!                         }
!
!                         bytes_output[j] += bytes_to_output;
!
!                         /* Are we at the end of the string? */
!                         more_col_wrapping = more_col_wrapping ||
!                             (*(this_line->ptr + bytes_output[j]) != 0);
                      }
!
!                     /* print a divider, middle columns only */
!                     if ((j + 1) % col_count)
                      {
!                         if (opt_border == 0)
!                             fputc(' ', fout);
!                         /* first line for values? */
!                         else if (line_count == 0 && col_line_count == 0)
!                             fputs(" | ", fout);
!                         /* next value is beyond height? */
!                         else if (line_count >= heights[j + 1])
!                             fputs("   ", fout);
!                         /* start of another newline string? */
!                         else if (col_line_count == 0)
!                             fputs(" : ", fout);
!                         else
!                         {
!                             /* Does the next column wrap to this line? */
!                             struct lineptr *this_line = &col_lineptrs[j+1][line_count];
!                             bool    string_done = *(this_line->ptr + bytes_output[j+1]) == 0;
!
!                             fputs(string_done ? "   " : " ; ", fout);
!                         }
                      }
                  }

!                 /* end of row border */
!                 if (opt_border == 2)
!                     fputs(" |", fout);
!                 fputc('\n', fout);
!                 col_line_count++;
!
!             } while (more_col_wrapping);
!
!             /*
!              * Check if any columns have line continuations due to \n in the
!              * cell.
!              */
!             line_count++;
!
!             /* Do we have more lines for any column value, i.e embedded \n? */
!             more_newline_values = false;
!             for (j = 0; j < col_count; j++)
!             {
!                 if (line_count < heights[j])
                  {
!                     if (col_lineptrs[j][line_count].ptr)
!                     {
!                         more_newline_values = true;
!                         bytes_output[j] = 0;
!                     }
                  }
              }
!
!         } while (more_newline_values);
      }

      if (opt->stop_table)
      {
          if (opt_border == 2 && !cancel_pressed)
!             _print_horizontal_line(col_count, width_wrap, opt_border, fout);

          /* print footers */
          if (footers && !opt_tuples_only && !cancel_pressed)
***************
*** 722,732 ****
      }

      /* clean up */
!     free(widths);
      free(heights);
      free(col_lineptrs);
      free(format_space);
!     free(complete);
      free(lineptr_list);
      for (i = 0; i < col_count; i++)
          free(format_buf[i]);
--- 888,902 ----
      }

      /* clean up */
!     free(width_header);
!     free(width_average);
!     free(width_max);
!     free(width_wrap);
      free(heights);
      free(col_lineptrs);
      free(format_space);
!     free(header_done);
!     free(bytes_output);
      free(lineptr_list);
      for (i = 0; i < col_count; i++)
          free(format_buf[i]);
***************
*** 754,760 ****
                  dheight = 1,
                  hformatsize = 0,
                  dformatsize = 0;
-     int            tmp = 0;
      char       *divider;
      unsigned int cell_count = 0;
      struct lineptr *hlineptr,
--- 924,929 ----
***************
*** 779,790 ****
      /* Find the maximum dimensions for the headers */
      for (i = 0; i < col_count; i++)
      {
!         int            height,
                      fs;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs);
!         if (tmp > hwidth)
!             hwidth = tmp;
          if (height > hheight)
              hheight = height;
          if (fs > hformatsize)
--- 948,960 ----
      /* Find the maximum dimensions for the headers */
      for (i = 0; i < col_count; i++)
      {
!         int            width,
!                     height,
                      fs;

!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs);
!         if (width > hwidth)
!             hwidth = width;
          if (height > hheight)
              hheight = height;
          if (fs > hformatsize)
***************
*** 799,805 ****
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
          int            numeric_locale_len;
!         int            height,
                      fs;

          if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
--- 969,976 ----
      for (i = 0, ptr = cells; *ptr; ptr++, i++)
      {
          int            numeric_locale_len;
!         int            width,
!                     height,
                      fs;

          if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
***************
*** 807,816 ****
          else
              numeric_locale_len = 0;

!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &fs);
!         tmp += numeric_locale_len;
!         if (tmp > dwidth)
!             dwidth = tmp;
          if (height > dheight)
              dheight = height;
          if (fs > dformatsize)
--- 978,987 ----
          else
              numeric_locale_len = 0;

!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs);
!         width += numeric_locale_len;
!         if (width > dwidth)
!             dwidth = width;
          if (height > dheight)
              dheight = height;
          if (fs > dformatsize)
***************
*** 1879,1884 ****
--- 2050,2056 ----
                                       opt, output);
              break;
          case PRINT_ALIGNED:
+         case PRINT_WRAP:
              if (opt->expanded)
                  print_aligned_vertical(title, headers, cells, footers, align,
                                         opt, output);
***************
*** 2066,2068 ****
--- 2238,2275 ----
      else
          thousands_sep = ".";
  }
+
+ /*
+  *    Returns the byte length to the end of the specified character
+  *  and number of display characters processed (useful if the string
+  *    is shorter then dpylen).
+  */
+ static int
+ strlen_max_width(unsigned char *str, int *target_width, int encoding)
+ {
+     unsigned char *start = str;
+     int curr_width = 0;
+
+     while (*str && curr_width < *target_width)
+     {
+         int char_width = PQdsplen((char *) str, encoding);
+
+         /*
+          *    If the display width of the new character causes
+          *    the string to exceed its target width, skip it
+          *    and return.  However, if this is the first character
+          *    of the string (*width == 0), we have to accept it.
+          */
+         if (*target_width - curr_width < char_width && curr_width != 0)
+             break;
+
+         str += PQmblen((char *)str, encoding);
+
+         curr_width += char_width;
+     }
+
+     *target_width = curr_width;
+
+     /* last byte */
+     return str - start;
+ }
Index: src/bin/psql/print.h
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v
retrieving revision 1.35
diff -c -c -r1.35 print.h
*** src/bin/psql/print.h    1 Jan 2008 19:45:56 -0000    1.35
--- src/bin/psql/print.h    21 Apr 2008 15:28:02 -0000
***************
*** 21,26 ****
--- 21,27 ----
      PRINT_NOTHING = 0,            /* to make sure someone initializes this */
      PRINT_UNALIGNED,
      PRINT_ALIGNED,
+     PRINT_WRAP,
      PRINT_HTML,
      PRINT_LATEX,
      PRINT_TROFF_MS
***************
*** 47,52 ****
--- 48,54 ----
                                   * decimal marker */
      char       *tableAttr;        /* attributes for HTML <table ...> */
      int            encoding;        /* character encoding */
+     int            columns;        /* target width for wrap format */
  } printTableOpt;



Re: Proposed patch - psql wraps at window width

From
Alvaro Herrera
Date:
Bruce Momjian wrote:

> !                     /* print a divider, middle columns only */
> !                     if ((j + 1) % col_count)
>                       {
> !                         if (opt_border == 0)
> !                             fputc(' ', fout);
> !                         /* first line for values? */
> !                         else if (line_count == 0 && col_line_count == 0)
> !                             fputs(" | ", fout);
> !                         /* next value is beyond height? */
> !                         else if (line_count >= heights[j + 1])
> !                             fputs("   ", fout);
> !                         /* start of another newline string? */
> !                         else if (col_line_count == 0)
> !                             fputs(" : ", fout);
> !                         else
> !                         {
> !                             /* Does the next column wrap to this line? */
> !                             struct lineptr *this_line = &col_lineptrs[j+1][line_count];
> !                             bool    string_done = *(this_line->ptr + bytes_output[j+1]) == 0;
> !
> !                             fputs(string_done ? "   " : " ; ", fout);
> !                         }
>                       }

I think it's a bad idea to use the same " : " separator in the two last
cases.  They are different and they should be displayed differently.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Re: Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
Alvaro Herrera wrote:
> Bruce Momjian wrote:
>
> > !                     /* print a divider, middle columns only */
> > !                     if ((j + 1) % col_count)
> >                       {
> > !                         if (opt_border == 0)
> > !                             fputc(' ', fout);
> > !                         /* first line for values? */
> > !                         else if (line_count == 0 && col_line_count == 0)
> > !                             fputs(" | ", fout);
> > !                         /* next value is beyond height? */
> > !                         else if (line_count >= heights[j + 1])
> > !                             fputs("   ", fout);
> > !                         /* start of another newline string? */
> > !                         else if (col_line_count == 0)
> > !                             fputs(" : ", fout);
> > !                         else
> > !                         {
> > !                             /* Does the next column wrap to this line? */
> > !                             struct lineptr *this_line = &col_lineptrs[j+1][line_count];
> > !                             bool    string_done = *(this_line->ptr + bytes_output[j+1]) == 0;
> > !
> > !                             fputs(string_done ? "   " : " ; ", fout);
> > !                         }
> >                       }
>
> I think it's a bad idea to use the same " : " separator in the two last
> cases.  They are different and they should be displayed differently.

I confirmed with Alvaro that he didn't notice the first uses a colon and
the second a semicolon, so he is OK.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +

Re: Proposed patch - psql wraps at window width

From
Alvaro Herrera
Date:
Alvaro Herrera wrote:
> Bruce Momjian wrote:
>

> > !                             fputs(string_done ? "   " : " ; ", fout);
> > !                         }
> >                       }
>
> I think it's a bad idea to use the same " : " separator in the two last
> cases.  They are different and they should be displayed differently.

Bruce noted by IM that I misread the ? : expression :-)  Sorry for the
noise.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

Re: Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
Bruce Momjian wrote:
> > I think it's a bad idea to use the same " : " separator in the two last
> > cases.  They are different and they should be displayed differently.
>
> I confirmed with Alvaro that he didn't notice the first uses a colon and
> the second a semicolon, so he is OK.

FYI, I added a comment to the patch so others wouldn't confuse this.
The ? : C syntax makes such confusion easy.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +