Extend EXPLAIN to support output in XML or JSON format.

There are probably still some adjustments to be made in the details
of the output, but this gets the basic structure in place.

Robert Haas
This commit is contained in:
Tom Lane 2009-08-10 05:46:50 +00:00
parent 18894c401f
commit 9bd27b7c9e
12 changed files with 1153 additions and 364 deletions

View File

@ -6,7 +6,7 @@
* Copyright (c) 2008-2009, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/contrib/auto_explain/auto_explain.c,v 1.6 2009/07/26 23:34:17 tgl Exp $
* $PostgreSQL: pgsql/contrib/auto_explain/auto_explain.c,v 1.7 2009/08/10 05:46:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -22,8 +22,16 @@ PG_MODULE_MAGIC;
static int auto_explain_log_min_duration = -1; /* msec or -1 */
static bool auto_explain_log_analyze = false;
static bool auto_explain_log_verbose = false;
static int auto_explain_log_format = EXPLAIN_FORMAT_TEXT;
static bool auto_explain_log_nested_statements = false;
static const struct config_enum_entry format_options[] = {
{"text", EXPLAIN_FORMAT_TEXT, false},
{"xml", EXPLAIN_FORMAT_XML, false},
{"json", EXPLAIN_FORMAT_JSON, false},
{NULL, 0, false}
};
/* Current nesting depth of ExecutorRun calls */
static int nesting_level = 0;
@ -84,6 +92,17 @@ _PG_init(void)
NULL,
NULL);
DefineCustomEnumVariable("auto_explain.log_format",
"EXPLAIN format to be used for plan logging.",
NULL,
&auto_explain_log_format,
EXPLAIN_FORMAT_TEXT,
format_options,
PGC_SUSET,
0,
NULL,
NULL);
DefineCustomBoolVariable("auto_explain.log_nested_statements",
"Log nested statements.",
NULL,
@ -201,6 +220,7 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
ExplainInitState(&es);
es.analyze = (queryDesc->doInstrument && auto_explain_log_analyze);
es.verbose = auto_explain_log_verbose;
es.format = auto_explain_log_format;
ExplainPrintPlan(&es, queryDesc);

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/auto-explain.sgml,v 1.3 2009/01/02 01:16:02 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/auto-explain.sgml,v 1.4 2009/08/10 05:46:50 tgl Exp $ -->
<sect1 id="auto-explain">
<title>auto_explain</title>
@ -102,6 +102,24 @@ LOAD 'auto_explain';
</listitem>
</varlistentry>
<varlistentry>
<term>
<varname>auto_explain.log_format</varname> (<type>enum</type>)
</term>
<indexterm>
<primary><varname>auto_explain.log_format</> configuration parameter</primary>
</indexterm>
<listitem>
<para>
<varname>auto_explain.log_format</varname> selects the
<command>EXPLAIN</> output format to be used.
The allowed values are <literal>text</literal>, <literal>xml</literal>,
and <literal>json</literal>. The default is text.
Only superusers can change this setting.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<varname>auto_explain.log_nested_statements</varname> (<type>boolean</type>)

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/explain.sgml,v 1.45 2009/07/26 23:34:17 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/explain.sgml,v 1.46 2009/08/10 05:46:50 tgl Exp $
PostgreSQL documentation
-->
@ -31,7 +31,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
EXPLAIN [ ( { ANALYZE <replaceable class="parameter">boolean</replaceable> | VERBOSE <replaceable class="parameter">boolean</replaceable> | COSTS <replaceable class="parameter">boolean</replaceable> } [, ...] ) ] <replaceable class="parameter">statement</replaceable>
EXPLAIN [ ( { ANALYZE <replaceable class="parameter">boolean</replaceable> | VERBOSE <replaceable class="parameter">boolean</replaceable> | COSTS <replaceable class="parameter">boolean</replaceable> | FORMAT { TEXT | XML | JSON } } [, ...] ) ] <replaceable class="parameter">statement</replaceable>
EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replaceable>
</synopsis>
</refsynopsisdiv>
@ -109,7 +109,7 @@ ROLLBACK;
<listitem>
<para>
Carry out the command and show the actual run times. This
parameter defaults to <command>FALSE</command>.
parameter defaults to <literal>FALSE</literal>.
</para>
</listitem>
</varlistentry>
@ -118,8 +118,12 @@ ROLLBACK;
<term><literal>VERBOSE</literal></term>
<listitem>
<para>
Include the output column list for each node in the plan tree. This
parameter defaults to <command>FALSE</command>.
Display additional information regarding the plan. Specifically, include
the output column list for each node in the plan tree, schema-qualify
table and function names, always label variables in expressions with
their range table alias, and always print the name of each trigger for
which statistics are displayed. This parameter defaults to
<literal>FALSE</literal>.
</para>
</listitem>
</varlistentry>
@ -130,7 +134,19 @@ ROLLBACK;
<para>
Include information on the estimated startup and total cost of each
plan node, as well as the estimated number of rows and the estimated
width of each row. This parameter defaults to <command>TRUE</command>.
width of each row. This parameter defaults to <literal>TRUE</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>FORMAT</literal></term>
<listitem>
<para>
Specify the output format, which can be TEXT, XML, or JSON.
XML or JSON output contains the same information as the text output
format, but is easier for programs to parse. This parameter defaults to
<literal>TEXT</literal>.
</para>
</listitem>
</varlistentry>

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
* Copyright (c) 2002-2009, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.98 2009/07/26 23:34:17 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.99 2009/08/10 05:46:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -685,9 +685,6 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
foreach(p, plan_list)
{
PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
bool is_last_query;
is_last_query = (lnext(p) == NULL);
if (IsA(pstmt, PlannedStmt))
{
@ -714,9 +711,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
/* put a blank line between plans */
if (!is_last_query)
appendStringInfoChar(es->str, '\n');
/* Separate plans with an appropriate separator */
if (lnext(p) != NULL)
ExplainSeparatePlans(es);
}
if (estate)

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.92 2009/06/11 14:49:04 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.93 2009/08/10 05:46:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1593,8 +1593,6 @@ map_xml_name_to_sql_identifier(char *name)
char *
map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
{
StringInfoData buf;
if (type_is_array(type))
{
ArrayType *array;
@ -1605,6 +1603,7 @@ map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
int num_elems;
Datum *elem_values;
bool *elem_nulls;
StringInfoData buf;
int i;
array = DatumGetArrayTypeP(value);
@ -1638,8 +1637,7 @@ map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
{
Oid typeOut;
bool isvarlena;
char *p,
*str;
char *str;
/*
* Special XSD formatting for some data types
@ -1788,35 +1786,50 @@ map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
return str;
/* otherwise, translate special characters as needed */
initStringInfo(&buf);
for (p = str; *p; p++)
{
switch (*p)
{
case '&':
appendStringInfoString(&buf, "&amp;");
break;
case '<':
appendStringInfoString(&buf, "&lt;");
break;
case '>':
appendStringInfoString(&buf, "&gt;");
break;
case '\r':
appendStringInfoString(&buf, "&#x0d;");
break;
default:
appendStringInfoCharMacro(&buf, *p);
break;
}
}
return buf.data;
return escape_xml(str);
}
}
/*
* Escape characters in text that have special meanings in XML.
*
* Returns a palloc'd string.
*
* NB: this is intentionally not dependent on libxml.
*/
char *
escape_xml(const char *str)
{
StringInfoData buf;
const char *p;
initStringInfo(&buf);
for (p = str; *p; p++)
{
switch (*p)
{
case '&':
appendStringInfoString(&buf, "&amp;");
break;
case '<':
appendStringInfoString(&buf, "&lt;");
break;
case '>':
appendStringInfoString(&buf, "&gt;");
break;
case '\r':
appendStringInfoString(&buf, "&#x0d;");
break;
default:
appendStringInfoCharMacro(&buf, *p);
break;
}
}
return buf.data;
}
static char *
_SPI_strdup(const char *s)
{

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.162 2009/06/11 14:49:05 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.163 2009/08/10 05:46:50 tgl Exp $
*
* NOTES
* Eventually, the index information should go through here, too.
@ -1298,6 +1298,32 @@ get_func_name(Oid funcid)
return NULL;
}
/*
* get_func_namespace
*
* Returns the pg_namespace OID associated with a given function.
*/
Oid
get_func_namespace(Oid funcid)
{
HeapTuple tp;
tp = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcid),
0, 0, 0);
if (HeapTupleIsValid(tp))
{
Form_pg_proc functup = (Form_pg_proc) GETSTRUCT(tp);
Oid result;
result = functup->pronamespace;
ReleaseSysCache(tp);
return result;
}
else
return InvalidOid;
}
/*
* get_func_rettype
* Given procedure id, return the function's result type.

View File

@ -91,7 +91,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/sort/tuplesort.c,v 1.92 2009/08/01 20:59:17 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/sort/tuplesort.c,v 1.93 2009/08/10 05:46:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -2200,21 +2200,20 @@ tuplesort_restorepos(Tuplesortstate *state)
}
/*
* tuplesort_explain - produce a line of information for EXPLAIN ANALYZE
* tuplesort_get_stats - extract summary statistics
*
* This can be called after tuplesort_performsort() finishes to obtain
* printable summary information about how the sort was performed.
*
* The result is a palloc'd string.
* spaceUsed is measured in kilobytes.
*/
char *
tuplesort_explain(Tuplesortstate *state)
void
tuplesort_get_stats(Tuplesortstate *state,
const char **sortMethod,
const char **spaceType,
long *spaceUsed)
{
char *result = (char *) palloc(100);
long spaceUsed;
/*
* Note: it might seem we should print both memory and disk usage for a
* Note: it might seem we should provide both memory and disk usage for a
* disk-based sort. However, the current code doesn't track memory space
* accurately once we have begun to return tuples to the caller (since we
* don't account for pfree's the caller is expected to do), so we cannot
@ -2223,38 +2222,34 @@ tuplesort_explain(Tuplesortstate *state)
* tell us how much is actually used in sortcontext?
*/
if (state->tapeset)
spaceUsed = LogicalTapeSetBlocks(state->tapeset) * (BLCKSZ / 1024);
{
*spaceType = "Disk";
*spaceUsed = LogicalTapeSetBlocks(state->tapeset) * (BLCKSZ / 1024);
}
else
spaceUsed = (state->allowedMem - state->availMem + 1023) / 1024;
{
*spaceType = "Memory";
*spaceUsed = (state->allowedMem - state->availMem + 1023) / 1024;
}
switch (state->status)
{
case TSS_SORTEDINMEM:
if (state->boundUsed)
snprintf(result, 100,
"Sort Method: top-N heapsort Memory: %ldkB",
spaceUsed);
*sortMethod = "top-N heapsort";
else
snprintf(result, 100,
"Sort Method: quicksort Memory: %ldkB",
spaceUsed);
*sortMethod = "quicksort";
break;
case TSS_SORTEDONTAPE:
snprintf(result, 100,
"Sort Method: external sort Disk: %ldkB",
spaceUsed);
*sortMethod = "external sort";
break;
case TSS_FINALMERGE:
snprintf(result, 100,
"Sort Method: external merge Disk: %ldkB",
spaceUsed);
*sortMethod = "external merge";
break;
default:
snprintf(result, 100, "sort still in progress");
*sortMethod = "still in progress";
break;
}
return result;
}

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/explain.h,v 1.40 2009/07/26 23:34:18 tgl Exp $
* $PostgreSQL: pgsql/src/include/commands/explain.h,v 1.41 2009/08/10 05:46:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -15,16 +15,26 @@
#include "executor/executor.h"
typedef enum ExplainFormat
{
EXPLAIN_FORMAT_TEXT,
EXPLAIN_FORMAT_XML,
EXPLAIN_FORMAT_JSON
} ExplainFormat;
typedef struct ExplainState
{
StringInfo str; /* output buffer */
/* options */
bool verbose; /* print plan targetlists */
bool verbose; /* be verbose */
bool analyze; /* print actual times */
bool costs; /* print costs */
ExplainFormat format; /* output format */
/* other states */
PlannedStmt *pstmt; /* top of plan */
List *rtable; /* range table */
int indent; /* current indentation level */
List *grouping_stack; /* format-specific grouping state */
} ExplainState;
/* Hook for plugins to get control in ExplainOneQuery() */
@ -54,4 +64,6 @@ extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);
extern void ExplainSeparatePlans(ExplainState *es);
#endif /* EXPLAIN_H */

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.128 2009/06/11 14:49:13 momjian Exp $
* $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.129 2009/08/10 05:46:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -76,6 +76,7 @@ extern Oid get_negator(Oid opno);
extern RegProcedure get_oprrest(Oid opno);
extern RegProcedure get_oprjoin(Oid opno);
extern char *get_func_name(Oid funcid);
extern Oid get_func_namespace(Oid funcid);
extern Oid get_func_rettype(Oid funcid);
extern int get_func_nargs(Oid funcid);
extern Oid get_func_signature(Oid funcid, Oid **argtypes, int *nargs);

View File

@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/tuplesort.h,v 1.33 2009/06/11 14:49:13 momjian Exp $
* $PostgreSQL: pgsql/src/include/utils/tuplesort.h,v 1.34 2009/08/10 05:46:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -84,7 +84,10 @@ extern bool tuplesort_getdatum(Tuplesortstate *state, bool forward,
extern void tuplesort_end(Tuplesortstate *state);
extern char *tuplesort_explain(Tuplesortstate *state);
extern void tuplesort_get_stats(Tuplesortstate *state,
const char **sortMethod,
const char **spaceType,
long *spaceUsed);
extern int tuplesort_merge_order(long allowedMem);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.28 2009/06/11 14:49:13 momjian Exp $
* $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.29 2009/08/10 05:46:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -70,6 +70,7 @@ extern xmltype *xmlpi(char *target, text *arg, bool arg_is_null, bool *result_is
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
extern text *xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg);
extern char *escape_xml(const char *str);
extern char *map_sql_identifier_to_xml_name(char *ident, bool fully_escaped, bool escape_period);
extern char *map_xml_name_to_sql_identifier(char *name);