Thread: Re: [HACKERS] Proposed patch - psql wraps at window width

Re: [HACKERS] Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
I have updated the documentation for this patch.  I consider it ready to
apply.  I think it is as close to a middle ground as we are going to
get.  Further adjustment will have to happen when we have more reports
from the field.

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

Bruce Momjian wrote:
> I have moved this discussion to hackers in hopes of getting more
> feedback, and moved the patch to a static URL:
>
>     ftp://momjian.us/pub/postgresql/mypatches/wrap
>
> This patch adds a new '\pset format wrapped' mode that wraps long values
> to fit the table on the user's screen, or in '\pset columns' columns in
> an output to file or pipe.  The documentation additions are at the top
> of the patch.
>
> Sample:
>
>     \pset format wrapped
>     Output format is wrapped.
>
>     \pset columns 70
>     Target column width for "wrap" format is 70.
>
>     SELECT 1, 2, repeat('a', 80), repeat('b', 80), E'a\nb\nc', 1
>     FROM generate_series(1,2)\g
>
>      ?column? | ?column? |   repeat   |   repeat    | ?column? | ?column?
>     ----------+----------+------------+-------------+----------+----------
>             1 |        2 | aaaaaaaaaa | bbbbbbbbbbb | a        |        1
>                          ; aaaaaaaaaa ; bbbbbbbbbbb : b
>                          ; aaaaaaaaaa ; bbbbbbbbbbb : c
>                          ; aaaaaaaaaa ; bbbbbbbbbbb
>                          ; aaaaaaaaaa ; bbbbbbbbbbb
>                          ; aaaaaaaaaa ; bbbbbbbbbbb
>                          ; aaaaaaaaaa ; bbbbbbbbbbb
>                          ; aaaaaaaaaa ; bbb
>             1 |        2 | aaaaaaaaaa | bbbbbbbbbbb | a        |        1
>                          ; aaaaaaaaaa ; bbbbbbbbbbb : b
>                          ; aaaaaaaaaa ; bbbbbbbbbbb : c
>                          ; aaaaaaaaaa ; bbbbbbbbbbb
>                          ; aaaaaaaaaa ; bbbbbbbbbbb
>                          ; aaaaaaaaaa ; bbbbbbbbbbb
>                          ; aaaaaaaaaa ; bbbbbbbbbbb
>                          ; aaaaaaaaaa ; bbb
>     (2 rows)
>
> You will notice:
>
>     o  I have pulled up newline values to appear in the same rows
>        as the wrapped text
>     o  Colons are used on the left for newline values
>     o  Semicolons are used on the left for wrapped values
>     o  There are no vertical bars for values that don't extend
>        to the wrapped or newline rows.  This is how our
>        newline display has always worked so it was copied
>        by the wrapped code
>     o  The left column has no indicator of wrapping or newlines
>        because there is no left border
>
> We could use dashes to indicated wrapped values, but we don't.  It would
> be nice if someone could do some multi-byte testing of this,
> particularly for characters that have a display width greater than one.
>
> I think this patch is ready for application.
>
> Should 'wrapped' be the default for certain operations, like \df?
> 'wrapped' mode is really good for a table that would fit the screen
> width except for a few wide values.

--
  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    29 Apr 2008 01:24:44 -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
!           to a target width of <literal>\pset columns</> or the
!           width of the screen.  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
***************
*** 1537,1542 ****
--- 1541,1557 ----
            </varlistentry>

            <varlistentry>
+           <term><literal>columns</literal></term>
+           <listitem>
+           <para>
+           Controls the target width for the <literal>wrapped</> format.
+           Zero (the default) causes the <literal>wrapped</> format to
+           affect only screen output.
+           </para>
+           </listitem>
+           </varlistentry>
+
+           <varlistentry>
            <term><literal>border</literal></term>
            <listitem>
            <para>
***************
*** 2707,2712 ****
--- 2722,2739 ----
    <title>Environment</title>

    <variablelist>
+
+    <varlistentry>
+     <term><envar>COLUMNS</envar></term>
+
+     <listitem>
+      <para>
+       Used for the <literal>wrapped</> format when screen width
+       detection fails.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><envar>PAGER</envar></term>

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    29 Apr 2008 01:24:45 -0000
***************
*** 1526,1531 ****
--- 1526,1534 ----
          case PRINT_ALIGNED:
              return "aligned";
              break;
+         case PRINT_WRAPPED:
+             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_WRAPPED;
          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 width for \"wrapped\" 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    29 Apr 2008 01:24:47 -0000
***************
*** 204,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
--- 204,211 ----
  /*
   * 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 newlines in display output
   *      result_format_size: Number of bytes required to store formatted representation of string
   */
  int
***************
*** 279,287 ****
      return width;
  }

  void
  pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
!              struct lineptr * lines, int count)
  {
      int            w,
                  chlen = 0;
--- 279,292 ----
      return width;
  }

+ /*
+  *  Filter out unprintable characters, companion to wcs_size.
+  *  Break input into lines based on \n.  lineptr[i].ptr == NULL
+  *    indicates the end of the array.
+  */
  void
  pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
!              struct lineptr *lines, int count)
  {
      int            w,
                  chlen = 0;
***************
*** 307,312 ****
--- 312,318 ----
                  if (count == 0)
                      exit(1);    /* Screwup */

+                 /* make next line point to remaining memory */
                  lines->ptr = ptr;
              }
              else if (*pwcs == '\r')        /* Linefeed */
***************
*** 353,364 ****
          }
          len -= chlen;
      }
-     *ptr++ = '\0';
      lines->width = linewidth;
!     lines++;
!     count--;
!     if (count > 0)
!         lines->ptr = NULL;
  }

  unsigned char *
--- 359,371 ----
          }
          len -= chlen;
      }
      lines->width = linewidth;
!     *ptr++ = '\0';            /* Terminate formatted string */
!
!     if (count == 0)
!         exit(1);    /* Screwup */
!
!     (lines+1)->ptr = NULL;    /* terminate line array */
  }

  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    29 Apr 2008 01:24:48 -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,
!                *max_width,
!                *width_wrap,
!                *width_average;
!     unsigned int *max_nl_lines,    /* value split by newlines */
!                 *curr_nl_line,
!                 *max_bytes;
      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 */

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

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

      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];
          }
      }
-     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 ? '+' : ' ');
                  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 = widths[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", widths[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, 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)
--- 449,856 ----

      if (col_count > 0)
      {
!         width_header = pg_local_calloc(col_count, sizeof(*width_header));
!         width_average = pg_local_calloc(col_count, sizeof(*width_average));
!         max_width = pg_local_calloc(col_count, sizeof(*max_width));
!         width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap));
!         max_nl_lines = pg_local_calloc(col_count, sizeof(*max_nl_lines));
!         curr_nl_line = pg_local_calloc(col_count, sizeof(*curr_nl_line));
          col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
!         max_bytes = pg_local_calloc(col_count, sizeof(*max_bytes));
          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;
!         max_width = NULL;
!         width_wrap = NULL;
!         max_nl_lines = NULL;
!         curr_nl_line = NULL;
          col_lineptrs = NULL;
!         max_bytes = NULL;
          format_buf = NULL;
!         header_done = NULL;
!         bytes_output = NULL;
      }

!     /* scan all column headers, find maximum width and max max_nl_lines */
      for (i = 0; i < col_count; i++)
      {
!         int            width,
!                     nl_lines,
!                     bytes_required;
!
!         pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &nl_lines, &bytes_required);
!         if (width > max_width[i])
!             max_width[i] = width;
!         if (nl_lines > max_nl_lines[i])
!             max_nl_lines[i] = nl_lines;
!         if (bytes_required > max_bytes[i])
!             max_bytes[i] = bytes_required;
!
!         width_header[i] = width;
      }

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

!         /* Get width, ignore nl_lines */
!         pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &nl_lines, &bytes_required);
!         if (opt_numeric_locale && opt_align[i % col_count] == 'r')
!         {
!             width += additional_numeric_locale_len(*ptr);
!             bytes_required += additional_numeric_locale_len(*ptr);
!         }
!
!         if (width > max_width[i % col_count])
!             max_width[i % col_count] = width;
!         if (nl_lines > max_nl_lines[i % col_count])
!             max_nl_lines[i % col_count] = nl_lines;
!         if (bytes_required > max_bytes[i % col_count])
!             max_bytes[i % col_count] = bytes_required;

!         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 += max_width[i];
!         total_header_width += width_header[i];
!     }

      /*
!      * At this point: max_width[] contains the max width of each column,
!      * max_nl_lines[] contains the max number of lines in each column,
!      * max_bytes[] contains the maximum storage space for formatting
!      * strings, width_total contains the giant width sum.  Now we allocate
!      * some memory for line pointers.
       */
!     for (i = 0; i < col_count; i++)
      {
!         /* Add entry for ptr == NULL array termination */
!         col_lineptrs[i] = pg_local_calloc(max_nl_lines[i] + 1,
!                                             sizeof(**col_lineptrs));

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

!         col_lineptrs[i]->ptr = format_buf[i];
!     }

!     /* Default word wrap to the full width, i.e. no word wrap */
!     for (i = 0; i < col_count; i++)
!         width_wrap[i] = max_width[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_WRAPPED)
!     {
!         /* Get terminal width */
!         if (opt->columns > 0)
!             output_columns = opt->columns;
!         else if (fout == stdout && isatty(fileno(stdout)))
!         {
! #ifdef TIOCGWINSZ
!             struct winsize screen_size;
!
!             if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
!                 output_columns = screen_size.ws_col;
!             else
! #endif
!             {
!                 const char *columns_env = getenv("COLUMNS");
!
!                 if (columns_env)
!                     output_columns = atoi(columns_env);
!             }
!         }
!
!         /*
!          * If available columns is positive...
!          * and greater than the width of the unshrinkable column headers
!          */
!         if (output_columns > 0 && output_columns >= total_header_width)
          {
!             /* While there is still excess width... */
!             while (width_total > output_columns)
!             {
!                 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 max_width.
!                  */
!                 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] +
!                                     max_width[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;
!
!                 /* Decrease width of target column by one. */
!                 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            curr_nl_line;

              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], max_nl_lines[i]);

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

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

!                     struct lineptr *this_line = col_lineptrs[i] + curr_nl_line;

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

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

!                         if (!(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)
!                             fputc(curr_nl_line ? '+' : ' ', fout);
                          else
!                             fprintf(fout, " |%c", curr_nl_line ? '+' : ' ');
                      }
                  }
!                 curr_nl_line++;

                  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, one loop per row */
      for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
      {
!         bool        more_lines;

          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], max_nl_lines[j]);
!             curr_nl_line[j] = 0;

!             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);
!             }
!         }
!
!         memset(bytes_output, 0, col_count * sizeof(int));
!
!         /*
!          *    Each time through this loop, one display line is output.
!          *    It can either be a full value or a partial value if embedded
!          *    newlines exist or if 'format=wrapping' mode is enabled.
!          */
!         do
          {
!             more_lines = 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++)
              {
!                 /* We have a valid array element, so index it */
!                 struct lineptr *this_line = &col_lineptrs[j][curr_nl_line[j]];
!                 int        bytes_to_output,  chars_to_output = width_wrap[j];
!
!                 /* Past newline lines so pad for other columns */
!                 if (!this_line->ptr)
!                     fprintf(fout, "%*s", width_wrap[j], "");
                  else
                  {
!                     /* 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;
+
+                     /* Do we have more text to wrap? */
+                     if (*(this_line->ptr + bytes_output[j]) != 0)
+                         more_lines = true;
                      else
                      {
!                         /* Advance to next newline line */
!                         curr_nl_line[j]++;
!                         if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
!                             more_lines = true;
!                         bytes_output[j] = 0;
                      }
                  }

!                 /* print a divider, middle columns only */
                  if ((j + 1) % col_count)
                  {
                      if (opt_border == 0)
                          fputc(' ', fout);
!                     /* Next value is beyond past newlines? */
!                     else if (col_lineptrs[j+1][curr_nl_line[j+1]].ptr == NULL)
!                         fputs("   ", fout);
!                     /* In wrapping of value? */
!                     else if (bytes_output[j+1] != 0)
!                         fputs(" ; ", fout);
!                     /* After first newline value */
!                     else if (curr_nl_line[j+1] != 0)
!                         fputs(" : ", fout);
                      else
!                     /* Ordinary line */
!                         fputs(" | ", fout);
                  }
+
              }
+
+             /* end of row border */
              if (opt_border == 2)
                  fputs(" |", fout);
              fputc('\n', fout);
!
!         } while (more_lines);
      }

      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,733 ****
      }

      /* 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]);
      free(format_buf);
--- 867,882 ----
      }

      /* clean up */
!     free(width_header);
!     free(width_average);
!     free(max_width);
!     free(width_wrap);
!     free(max_nl_lines);
!     free(curr_nl_line);
      free(col_lineptrs);
!     free(max_bytes);
!     free(header_done);
!     free(bytes_output);
      for (i = 0; i < col_count; i++)
          free(format_buf[i]);
      free(format_buf);
***************
*** 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)
***************
*** 821,828 ****
       * 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);
--- 971,978 ----
       * We now have all the information we need to setup the formatting
       * structures
       */
!     dlineptr = pg_local_malloc((sizeof(*dlineptr) + 1) * dheight);
!     hlineptr = pg_local_malloc((sizeof(*hlineptr) + 1) * hheight);

      dlineptr->ptr = pg_local_malloc(dformatsize);
      hlineptr->ptr = pg_local_malloc(hformatsize);
***************
*** 910,916 ****
                  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
--- 1060,1066 ----
                  fprintf(fout, "%-s%*s", hlineptr[line_count].ptr,
                          hwidth - hlineptr[line_count].width, "");

!                 if (!hlineptr[line_count + 1].ptr)
                      hcomplete = 1;
              }
              else
***************
*** 943,949 ****
                                  dwidth - dlineptr[line_count].width, "");
                  }

!                 if (line_count == dheight - 1 || !dlineptr[line_count + 1].ptr)
                      dcomplete = 1;
              }
              else
--- 1093,1099 ----
                                  dwidth - dlineptr[line_count].width, "");
                  }

!                 if (!dlineptr[line_count + 1].ptr)
                      dcomplete = 1;
              }
              else
***************
*** 1879,1884 ****
--- 2029,2035 ----
                                       opt, output);
              break;
          case PRINT_ALIGNED:
+         case PRINT_WRAPPED:
              if (opt->expanded)
                  print_aligned_vertical(title, headers, cells, footers, align,
                                         opt, output);
***************
*** 2066,2068 ****
--- 2217,2254 ----
      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    29 Apr 2008 01:24:48 -0000
***************
*** 21,26 ****
--- 21,27 ----
      PRINT_NOTHING = 0,            /* to make sure someone initializes this */
      PRINT_UNALIGNED,
      PRINT_ALIGNED,
+     PRINT_WRAPPED,
      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 wrapped format */
  } printTableOpt;



Re: [HACKERS] Proposed patch - psql wraps at window width

From
Bryce Nesbitt
Date:
Bruce Momjian wrote:
> I have updated the documentation for this patch.  I consider it ready to
> apply.  I think it is as close to a middle ground as we are going to
> get.  Further adjustment will have to happen when we have more reports
> from the field.
I heard a pretty compelling argument to make "wrapped" part of "aligned", and thus I think the patch  is ready to go
onlyafter adjusting the user-facing syntax: 


\pset border 2
\pset format aligned
Output format is aligned, no wrapping.

\pset format aligned autowrap
Output format is aligned, with automatic wrapping to the window width for terminals.

\pset format aligned 80
Output format is aligned, with a target width of 80 characters.

\a
Output format is unaligned, no wrapping.
\a
Output format is aligned, with a target width of 80 characters.



If the wrapping code can't fit the column headings in the wrap width, it gives up and produces aligned output.  To do
otherwiseis totally unreadable.  Please give the patch a try, before complaining about this particular aspect of it. 



Re: [HACKERS] Proposed patch - psql wraps at window width

From
Bruce Momjian
Date:
Bryce Nesbitt wrote:
> Bruce Momjian wrote:
> > I have updated the documentation for this patch.  I consider it ready to
> > apply.  I think it is as close to a middle ground as we are going to
> > get.  Further adjustment will have to happen when we have more reports
> > from the field.
>
> I heard a pretty compelling argument to make "wrapped" part of "aligned",
> and thus I think the patch  is ready to go only after adjusting the
> user-facing syntax:
>
>
> \pset border 2
> \pset format aligned
> Output format is aligned, no wrapping.
>
> \pset format aligned autowrap
> Output format is aligned, with automatic wrapping to the window width for terminals.
>
> \pset format aligned 80
> Output format is aligned, with a target width of 80 characters.
>
> \a
> Output format is unaligned, no wrapping.
> \a
> Output format is aligned, with a target width of 80 characters.
>
>
>
> If the wrapping code can't fit the column headings in the wrap width,
> it gives up and produces aligned output.  To do otherwise is totally
> unreadable.  Please give the patch a try, before complaining about this
> particular aspect of it.

Uh, well, we can do that, though looking at the psql code \pset only
wants two arguments.  We would have to modify how \pset works.  Also, I
am afraid making wrapping part of aligned is overly complicating the
API.  For example, I specificially kept \pset columns rather than
allowing a third argument to pset because it was starting to look too
complicated to describe in the manual.

I am not 100% sure myself so hopefully others will comment.

--
  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: [HACKERS] Proposed patch - psql wraps at window width

From
Alvaro Herrera
Date:
Bruce Momjian wrote:
> Bryce Nesbitt wrote:
> > Bruce Momjian wrote:
> > > I have updated the documentation for this patch.  I consider it ready to
> > > apply.  I think it is as close to a middle ground as we are going to
> > > get.  Further adjustment will have to happen when we have more reports
> > > from the field.
> >
> > I heard a pretty compelling argument to make "wrapped" part of "aligned",
> > and thus I think the patch  is ready to go only after adjusting the
> > user-facing syntax:

I think this is what makes the most sense of what I've seen so far.
Wrapped is a special case of aligned anyway (there's no "wrapped
unaligned" or "wrapped HTML" for example, nor would we want there to
be.)

> > Output format is aligned, no wrapping.
> >
> > \pset format aligned autowrap
> > Output format is aligned, with automatic wrapping to the window width for terminals.
> >
> > \pset format aligned 80
> > Output format is aligned, with a target width of 80 characters.

> Uh, well, we can do that, though looking at the psql code \pset only
> wants two arguments.

I think that could be fixed easily by having the syntax be something
like

\pset format aligned:80
\pset format aligned:autowrap

etc.

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

Re: [HACKERS] Proposed patch - psql wraps at window width

From
Gregory Stark
Date:
"Alvaro Herrera" <alvherre@commandprompt.com> writes:

> Bruce Momjian wrote:
>> Bryce Nesbitt wrote:
>> > Bruce Momjian wrote:
>> > > I have updated the documentation for this patch.  I consider it ready to
>> > > apply.  I think it is as close to a middle ground as we are going to
>> > > get.  Further adjustment will have to happen when we have more reports
>> > > from the field.
>> >
>> > I heard a pretty compelling argument to make "wrapped" part of "aligned",
>> > and thus I think the patch  is ready to go only after adjusting the
>> > user-facing syntax:
>
> I think this is what makes the most sense of what I've seen so far.
> Wrapped is a special case of aligned anyway (there's no "wrapped
> unaligned" or "wrapped HTML" for example, nor would we want there to
> be.)

Well there's no unaligned HTML or aligned unaligned either...


> I think that could be fixed easily by having the syntax be something
> like
>
> \pset format aligned:80
> \pset format aligned:autowrap

I suppose. It seems kind of inconvenient though, what advantage does it have?


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

Re: [HACKERS] Proposed patch - psql wraps at window width

From
Alvaro Herrera
Date:
Gregory Stark wrote:
> "Alvaro Herrera" <alvherre@commandprompt.com> writes:

> > I think that could be fixed easily by having the syntax be something
> > like
> >
> > \pset format aligned:80
> > \pset format aligned:autowrap
>
> I suppose. It seems kind of inconvenient though, what advantage does it have?

Over what?

I think having a separate "\pset format wrapped" does not make much
sense.  The fact that it's wrapped is not a new format in itself, just a
property of the aligned format.

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

Re: [HACKERS] Proposed patch - psql wraps at window width

From
Gregory Stark
Date:
"Alvaro Herrera" <alvherre@commandprompt.com> writes:

> The fact that it's wrapped is not a new format in itself, just a property of
> the aligned format.

Well I agree I just don't see any benefit to presenting it that way. I mean,
sure "wrapped" means "aligned and wrapped", but it's just shorthand notation
anyways.

In fact, it seems the only reason to separate the two like you're describing
would be if it *were* possible to have a wrapped html. Then your notation
could handle things "\pset format wrapped" couldn't:

\pset format html:autowrap

But I think we agree that isn't happening so why spell it "aligned:autowrap"
instead of just "wrap"?

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