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

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



pgsql-patches by date:

Previous
From: Tom Lane
Date:
Subject: Re: [HACKERS] Removing typename from A_Const (was: Empty arrays with ARRAY[])
Next
From: Bryce Nesbitt
Date:
Subject: Re: [HACKERS] Proposed patch - psql wraps at window width