Re: machine-readable explain output v4

From: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
To: Andres Freund <andres(at)anarazel(dot)de>
Cc: pgsql-hackers(at)postgresql(dot)org, Andrew Dunstan <andrew(at)dunslane(dot)net>, Robert Haas <robertmhaas(at)gmail(dot)com>
Subject: Re: machine-readable explain output v4
Date: 2009-08-10 00:53:16
Message-ID: 3387.1249865596@sss.pgh.pa.us
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Andres Freund <andres(at)anarazel(dot)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 list of 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 in JSON).
*/
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 containing object (if any).
*
* If labeled is true, the group members will be labeled properties,
* while if it's false, they'll be 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 this grouping 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_NOWHITESPACE to suppress the whitespace we'd normally add.
*
* XML tag names can't contain white space, so we replace any spaces 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, '\"');
}

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Robert Haas 2009-08-10 01:00:12 Re: machine-readable explain output v4
Previous Message Andrew Dunstan 2009-08-10 00:48:29 Re: machine-readable explain output v4