Attempt to fix EXPLAIN (FORMAT YAML) quoting to behave sanely.
authorRobert Haas
Wed, 9 Jun 2010 02:39:34 +0000 (02:39 +0000)
committerRobert Haas
Wed, 9 Jun 2010 02:39:34 +0000 (02:39 +0000)
The previous code failed to quote in many cases where quoting was necessary -
YAML has loads of special characters, including -:[]{},"'|*& - so quote much
more aggressively, and only refrain from quoting things where it seems fairly
clear that it isn't necessary.

Per report from Dean Rasheed.

src/backend/commands/explain.c

index 2409a01e2dd5f9b8439d2264aa90f3718e2ef6f8..d6a0f65acff1f0a1712e12887f9b158627cb5c12 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.204 2010/02/26 02:00:39 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.205 2010/06/09 02:39:34 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2152,22 +2152,48 @@ escape_json(StringInfo buf, const char *str)
 }
 
 /*
- * YAML is a superset of JSON: if we find quotable characters, we call
- * escape_json.  If not, we emit the property unquoted for better readability.
+ * YAML is a superset of JSON, so we can use JSON escaping when escaping is
+ * needed.  However, some things that need to be quoted in JSON don't require
+ * quoting in YAML, and we prefer not to quote unnecessarily, to improve
+ * readability.
+ *
+ * Unfortunately, the YAML quoting rules are ridiculously complicated -- as
+ * documented in sections 5.3 and 7.3.3 of http://yaml.org/spec/1.2/spec.html
+ * -- and it doesn't seem worth expending a large amount of energy to avoid
+ * all unnecessary quoting, so we just do something (sort of) simple: we quote
+ * any string which is empty; any string which contains characters other than
+ * alphanumerics, period, underscore, or space; or begins or ends with a
+ * space.  The exception for period is mostly so that floating-point numbers
+ * (e.g., cost values) won't be quoted.
  */
 static void
 escape_yaml(StringInfo buf, const char *str)
 {
-   const char *p;
+   bool        needs_quoting = false;
 
-   for (p = str; *p; p++)
+#define is_safe_yaml(x) \
+   (isalnum(((unsigned char) x)) || (x) == '.' || (x) == '_')
+
+   if (!is_safe_yaml(str[0]))
+       needs_quoting = true;
+   else
    {
-       if ((unsigned char) *p < ' ' || strchr("\"\\\b\f\n\r\t", *p))
+       const char *p;
+
+       for (p = str; *p; p++)
        {
-           escape_json(buf, str);
-           return;
+           if (*p != ' ' && !is_safe_yaml(*p))
+           {
+               needs_quoting = true;
+               break;
+           }
        }
+       if (!*p && p[-1] == ' ')
+           needs_quoting = true;
    }
 
-   appendStringInfo(buf, "%s", str);
+   if (needs_quoting)
+       escape_json(buf, str);
+   else
+       appendStringInfoString(buf, str);
 }