OSDN Git Service

Attempt to fix EXPLAIN (FORMAT YAML) quoting to behave sanely.
authorRobert Haas <rhaas@postgresql.org>
Wed, 9 Jun 2010 02:39:34 +0000 (02:39 +0000)
committerRobert Haas <rhaas@postgresql.org>
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 2409a01..d6a0f65 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);
 }