Re: Proposed patch - psql wraps at window width - Mailing list pgsql-patches

From Bruce Momjian
Subject Re: Proposed patch - psql wraps at window width
Date
Msg-id 200804200411.m3K4Bjm18947@momjian.us
Whole thread Raw
In response to Re: Proposed patch - psql wraps at window width  (Bryce Nesbitt <bryce2@obviously.com>)
Responses Re: Proposed patch - psql wraps at window width
List pgsql-patches
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;



pgsql-patches by date:

Previous
From: Tom Lane
Date:
Subject: Re: float4/float8/int64 passed by value with tsearchfixup
Next
From: Zoltan Boszormenyi
Date:
Subject: Re: float4/float8/int64 passed by value with tsearch fixup