Re: Spaces before newlines in view definitions in 9.3 - Mailing list pgsql-bugs

From Tom Lane
Subject Re: Spaces before newlines in view definitions in 9.3
Date
Msg-id 30611.1383949494@sss.pgh.pa.us
Whole thread Raw
In response to Spaces before newlines in view definitions in 9.3  (Joe Abbate <jma@freedomcircle.com>)
Responses Re: Spaces before newlines in view definitions in 9.3  (Joe Abbate <jma@freedomcircle.com>)
List pgsql-bugs
Joe Abbate <jma@freedomcircle.com> writes:
> View definition:
>  SELECT t1.c1,
>     t1.c2
>    FROM t1;

> It may not be immediately obvious but there is a space after the
> "t1.c1," and before the first newline.  In 9.2 and previous releases,
> the view definition is:

>  SELECT t1.c1, t1.c2
>    FROM t1;

Yeah, this was changed by commit 2f582f76b1945929ff07116cd4639747ce9bb8a1,
which added newlines to the output without making any attempt to suppress
the spaces that had been printed before.

> The problem is that the string comes back, e.g., from pg_get_viewdef()
> with those extra spaces before the newlines, e.g.,

> " SELECT t1.c1, \n    t1.c3 * 2 AS mc3\n   FROM t1;

> and YAML has a way displaying a text string nicely so that it can be
> recovered when it's read back, but it *doesn't* work if there are
> invisible characters such as tabs or spaces before a newline because
> obviously one can't tell how many or of what kind they are.

Hm.  I am not sure whether to consider that a significant complaint or
not, because in reality the ruleutils code has been pretty sloppy about
trailing whitespace ever since it grew any "pretty printing" capability
at all.  Looking for trailing whitespace in the ruleutils regression test,
I find it in "UNION ALL" and "INSERT ... VALUES" constructs, which were
that way long before that patch, and there are probably more cases that
don't happen to get exercised by the regression tests.  That patch
certainly made things worse, but it's not like pre-9.3 output was
YAML-safe.

Having said that, this seems relatively easy to fix by adjusting
appendContextKeyword to delete any trailing spaces that are immediately
before the newline it inserts.  AFAICS that's the only place in ruleutils
that inserts newlines that might follow a space.  get_target_list also
needs to do that when transposing a newline-started field value into the
main buffer; but that complexity is offset because it no longer need
consider the possibility of spaces before said newline.  I've tested the
attached patch and it seems to do what's wanted (note: I didn't bother
including the regression test output changes in the patch, as they're
bulky and boring).

I think that this is a reasonable thing to fix, but I'm not sure
if we ought to back-patch it into 9.3 or not.  The pretty-printing
change evidently broke your use-case but I'm thinking you weren't
pushing it very hard.

Comments?

            regards, tom lane

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 915fb7a..139939d 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** get_target_list(List *targetList, depars
*** 4479,4520 ****
          /* Consider line-wrapping if enabled */
          if (PRETTY_INDENT(context) && context->wrapColumn >= 0)
          {
!             int            leading_nl_pos = -1;
!             char       *trailing_nl;
!             int            pos;

!             /* Does the new field start with whitespace plus a new line? */
!             for (pos = 0; pos < targetbuf.len; pos++)
              {
!                 if (targetbuf.data[pos] == '\n')
!                 {
!                     leading_nl_pos = pos;
!                     break;
!                 }
!                 if (targetbuf.data[pos] != ' ')
!                     break;
              }
-
-             /* Locate the start of the current line in the output buffer */
-             trailing_nl = strrchr(buf->data, '\n');
-             if (trailing_nl == NULL)
-                 trailing_nl = buf->data;
              else
!                 trailing_nl++;

!             /*
!              * If the field we're adding is the first in the list, or it
!              * already has a leading newline, don't add anything. Otherwise,
!              * add a newline, plus some indentation, if either the new field
!              * would cause an overflow or the last field used more than one
!              * line.
!              */
!             if (colno > 1 &&
!                 leading_nl_pos == -1 &&
!                 ((strlen(trailing_nl) + strlen(targetbuf.data) > context->wrapColumn) ||
!                  last_was_multiline))
!                 appendContextKeyword(context, "", -PRETTYINDENT_STD,
!                                      PRETTYINDENT_STD, PRETTYINDENT_VAR);

              /* Remember this field's multiline status for next iteration */
              last_was_multiline =
--- 4479,4521 ----
          /* Consider line-wrapping if enabled */
          if (PRETTY_INDENT(context) && context->wrapColumn >= 0)
          {
!             int            leading_nl_pos;

!             /* Does the new field start with a new line? */
!             if (targetbuf.len > 0 && targetbuf.data[0] == '\n')
!                 leading_nl_pos = 0;
!             else
!                 leading_nl_pos = -1;
!
!             /* If so, we shouldn't add anything */
!             if (leading_nl_pos >= 0)
              {
!                 /* instead, remove any trailing spaces currently in buf */
!                 while (buf->len > 0 && buf->data[buf->len - 1] == ' ')
!                     buf->data[--(buf->len)] = '\0';
              }
              else
!             {
!                 char       *trailing_nl;

!                 /* Locate the start of the current line in the output buffer */
!                 trailing_nl = strrchr(buf->data, '\n');
!                 if (trailing_nl == NULL)
!                     trailing_nl = buf->data;
!                 else
!                     trailing_nl++;
!
!                 /*
!                  * Add a newline, plus some indentation, if the new field is
!                  * not the first and either the new field would cause an
!                  * overflow or the last field used more than one line.
!                  */
!                 if (colno > 1 &&
!                     ((strlen(trailing_nl) + strlen(targetbuf.data) > context->wrapColumn) ||
!                      last_was_multiline))
!                     appendContextKeyword(context, "", -PRETTYINDENT_STD,
!                                          PRETTYINDENT_STD, PRETTYINDENT_VAR);
!             }

              /* Remember this field's multiline status for next iteration */
              last_was_multiline =
*************** static void
*** 6236,6256 ****
  appendContextKeyword(deparse_context *context, const char *str,
                       int indentBefore, int indentAfter, int indentPlus)
  {
      if (PRETTY_INDENT(context))
      {
          context->indentLevel += indentBefore;

!         appendStringInfoChar(context->buf, '\n');
!         appendStringInfoSpaces(context->buf,
                                 Max(context->indentLevel, 0) + indentPlus);
!         appendStringInfoString(context->buf, str);

          context->indentLevel += indentAfter;
          if (context->indentLevel < 0)
              context->indentLevel = 0;
      }
      else
!         appendStringInfoString(context->buf, str);
  }

  /*
--- 6237,6264 ----
  appendContextKeyword(deparse_context *context, const char *str,
                       int indentBefore, int indentAfter, int indentPlus)
  {
+     StringInfo    buf = context->buf;
+
      if (PRETTY_INDENT(context))
      {
          context->indentLevel += indentBefore;

!         /* remove any trailing spaces currently in the buffer ... */
!         while (buf->len > 0 && buf->data[buf->len - 1] == ' ')
!             buf->data[--(buf->len)] = '\0';
!         /* ... then add a newline and some spaces */
!         appendStringInfoChar(buf, '\n');
!         appendStringInfoSpaces(buf,
                                 Max(context->indentLevel, 0) + indentPlus);
!
!         appendStringInfoString(buf, str);

          context->indentLevel += indentAfter;
          if (context->indentLevel < 0)
              context->indentLevel = 0;
      }
      else
!         appendStringInfoString(buf, str);
  }

  /*

pgsql-bugs by date:

Previous
From: John R Pierce
Date:
Subject: Re: BUG #8583: I can't install this product
Next
From: Joe Abbate
Date:
Subject: Re: Spaces before newlines in view definitions in 9.3