Re: machine-readable explain output v4 - Mailing list pgsql-hackers

From Tom Lane
Subject Re: machine-readable explain output v4
Date
Msg-id 3387.1249865596@sss.pgh.pa.us
Whole thread Raw
In response to Re: machine-readable explain output v4  (Andres Freund <andres@anarazel.de>)
Responses Re: machine-readable explain output v4  (Robert Haas <robertmhaas@gmail.com>)
Re: machine-readable explain output v4  (Andres Freund <andres@anarazel.de>)
List pgsql-hackers
Andres Freund <andres@anarazel.de> writes:
> On Monday 10 August 2009 01:21:35 Andrew Dunstan wrote:
>> That ";" after the attribute is almost certainly wrong. This is a classic
>> case of what I was talking about a month or two ago. Building up XML (or
>> any structured doc, really, XML is not special in this regard) by ad hoc
>> methods is horribly error prone. if you don't want to rely on libxml, then
>> I think you need to develop a lightweight abstraction rather than just
>> appending to a StringInfo.

> While it would be possible to add another step inbetween and generate a format 
> neutral tree and generate the different formats out of it I am not sure that 
> this is worthwile.
> The current text format will need to stay special cased anyway because its far 
> to inconsistent to generate it from anything abstract and I don't see any 
> completely new formats coming (i.e. not just optional parts)?

The patch as-submitted does have a lightweight abstraction layer of
sorts, but the main code line feels free to ignore that and hack around
it.  I agree that we can't avoid special-casing the text output, but
I'm trying to improve the encapsulation otherwise.  What I've got at the
moment is attached.  I'd be interested in comments on the grouping
notion in particular --- I reverse-engineered that out of what the code
was doing, and I'm sure it could use improvement.
        regards, tom lane


/** Explain a property, such as sort keys or targets, that takes the form of* a list of unlabeled items.  "data" is a
listof C strings.*/
 
static void
ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
{ListCell   *lc;bool        first = true;
switch (es->format){    case EXPLAIN_FORMAT_TEXT:        appendStringInfoSpaces(es->str, es->indent * 2);
appendStringInfo(es->str,"  %s: ", qlabel);        foreach(lc, data)        {            if (!first)
appendStringInfoString(es->str,", ");            appendStringInfoString(es->str, (const char *) lfirst(lc));
first= false;        }        appendStringInfoChar(es->str, '\n');        break;
 
    case EXPLAIN_FORMAT_XML:        ExplainXMLTag(qlabel, X_OPENING, es);        foreach(lc, data)        {
char  *str;
 
            appendStringInfoSpaces(es->str, es->indent * 2 + 2);            appendStringInfoString(es->str, "<Item>");
         str = escape_xml((const char *) lfirst(lc));            appendStringInfoString(es->str, str);
pfree(str);           appendStringInfoString(es->str, "</Item>\n");        }        ExplainXMLTag(qlabel, X_CLOSING,
es);       break;
 
    case EXPLAIN_FORMAT_JSON:        ExplainJSONLineEnding(es);        appendStringInfoSpaces(es->str, es->indent * 2);
      escape_json(es->str, qlabel);        appendStringInfoString(es->str, ": [");        foreach(lc, data)        {
       if (!first)                appendStringInfoString(es->str, ", ");            escape_json(es->str, (const char *)
lfirst(lc));           first = false;        }        appendStringInfoChar(es->str, ']');        break;}
 
}

#define ExplainPropertyText(qlabel, value, es)  \ExplainProperty(qlabel, value, false, es)

/** Explain a simple property.** If "numeric" is true, the value is a number (or other value that* doesn't need quoting
inJSON).*/
 
static void
ExplainProperty(const char *qlabel, const char *value, bool numeric,            ExplainState *es)
{switch (es->format){    case EXPLAIN_FORMAT_TEXT:        appendStringInfoSpaces(es->str, es->indent * 2);
appendStringInfo(es->str,"  %s: %s\n", qlabel, value);        break;
 
    case EXPLAIN_FORMAT_XML:        {            char   *str;
            appendStringInfoSpaces(es->str, es->indent * 2);            ExplainXMLTag(qlabel, X_OPENING |
X_NOWHITESPACE,es);            str = escape_xml(value);            appendStringInfoString(es->str, str);
pfree(str);           ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);            appendStringInfoChar(es->str,
'\n');       }        break;
 
    case EXPLAIN_FORMAT_JSON:        ExplainJSONLineEnding(es);        appendStringInfoSpaces(es->str, es->indent * 2);
      escape_json(es->str, qlabel);        appendStringInfoString(es->str, ": ");        if (numeric)
appendStringInfoString(es->str,value);        else            escape_json(es->str, value);        break;}
 
}

/** Explain an integer-valued property.*/
static void
ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es)
{char    buf[32];
snprintf(buf, sizeof(buf), "%d", value);ExplainProperty(qlabel, buf, true, es);
}

/** Explain a long-integer-valued property.*/
static void
ExplainPropertyLong(const char *qlabel, long value, ExplainState *es)
{char    buf[32];
snprintf(buf, sizeof(buf), "%ld", value);ExplainProperty(qlabel, buf, true, es);
}

/** Explain a float-valued property, using the specified number of* fractional digits.*/
static void
ExplainPropertyFloat(const char *qlabel, double value, int ndigits,                 ExplainState *es)
{char    buf[256];
snprintf(buf, sizeof(buf), "%.*f", ndigits, value);ExplainProperty(qlabel, buf, true, es);
}

/** Open a group of related objects.** objtype is the type of the group object, labelname is its label within* a
containingobject (if any).** If labeled is true, the group members will be labeled properties,* while if it's false,
they'llbe unlabeled objects.*/
 
static void
ExplainOpenGroup(const char *objtype, const char *labelname,             bool labeled, ExplainState *es)
{switch (es->format){    case EXPLAIN_FORMAT_TEXT:        /* nothing to do */        break;
    case EXPLAIN_FORMAT_XML:        ExplainXMLTag(objtype, X_OPENING, es);        break;
    case EXPLAIN_FORMAT_JSON:        ExplainJSONLineEnding(es);        appendStringInfoSpaces(es->str, 2 * es->indent);
      if (labelname)        {            escape_json(es->str, labelname);            appendStringInfoString(es->str, ":
");       }        appendStringInfoChar(es->str, labeled ? '{' : '[');
 
        /*         * In JSON mode, the grouping_stack is an integer list.  0 means         * we've emitted nothing at
thisgrouping level, 1 means we've         * emitted something (and so the next item needs a comma).         * See
ExplainJSONLineEnding().        */        es->grouping_stack = lcons_int(0, es->grouping_stack);
break;}es->indent++;
}

/** Close a group of related objects.* Parameters must match the corresponding ExplainOpenGroup call.*/
static void
ExplainCloseGroup(const char *objtype, const char *labelname,              bool labeled, ExplainState *es)
{es->indent--;switch (es->format){    case EXPLAIN_FORMAT_TEXT:        /* nothing to do */        break;
    case EXPLAIN_FORMAT_XML:        ExplainXMLTag(objtype, X_CLOSING, es);        break;
    case EXPLAIN_FORMAT_JSON:        appendStringInfoChar(es->str, '\n');        appendStringInfoSpaces(es->str, 2 *
es->indent);       appendStringInfoChar(es->str, labeled ? '}' : ']');        es->grouping_stack =
list_delete_first(es->grouping_stack);       break;}
 
}

/** Emit opening or closing XML tag.** "flags" must contain X_OPENING or X_CLOSING, which optionally can be* OR'ed with
X_NOWHITESPACEto suppress the whitespace we'd normally add.** XML tag names can't contain white space, so we replace
anyspaces in* "tagname" with dashes.*/
 
static void
ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
{const char *s;
if ((flags & X_NOWHITESPACE) == 0)    appendStringInfoSpaces(es->str, 2 *
es->indent);appendStringInfoCharMacro(es->str,'<');if ((flags & X_CLOSING) != 0)    appendStringInfoCharMacro(es->str,
'/');for(s = tagname; *s; s++)    appendStringInfoCharMacro(es->str, (*s == ' ') ? '-' :
*s);appendStringInfoCharMacro(es->str,'>');if ((flags & X_NOWHITESPACE) == 0)    appendStringInfoCharMacro(es->str,
'\n');
}

/** Emit a JSON line ending.** JSON requires a comma after each property but the last.  To facilitate this,* in JSON
mode,the text emitted for each property begins just prior to the* preceding line-break (and comma, if applicable).*/
 
static void
ExplainJSONLineEnding(ExplainState *es)
{Assert(es->format == EXPLAIN_FORMAT_JSON);if (linitial_int(es->grouping_stack) != 0)    appendStringInfoChar(es->str,
',');else   linitial_int(es->grouping_stack) = 1;appendStringInfoChar(es->str, '\n');
 
}

/** Produce a JSON string literal, properly escaping characters in the text.*/
static void
escape_json(StringInfo buf, const char *str)
{const char *p;
appendStringInfoCharMacro(buf, '\"');for (p = str; *p; p++){    switch (*p)    {        case '\b':
appendStringInfoString(buf,"\\b");            break;        case '\f':            appendStringInfoString(buf, "\\f");
        break;        case '\n':            appendStringInfoString(buf, "\\n");            break;        case '\r':
      appendStringInfoString(buf, "\\r");            break;        case '\t':            appendStringInfoString(buf,
"\\t");           break;        case '"':            appendStringInfoString(buf, "\\\"");            break;        case
'\\':           appendStringInfoString(buf, "\\\\");            break;        default:            if ((unsigned char)
*p< ' ')                appendStringInfo(buf, "\\u%04x", (int) *p);            else
appendStringInfoCharMacro(buf,*p);            break;    }}appendStringInfoCharMacro(buf, '\"');
 
}


pgsql-hackers by date:

Previous
From: Andrew Dunstan
Date:
Subject: Re: machine-readable explain output v4
Next
From: Robert Haas
Date:
Subject: Re: machine-readable explain output v4