aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/explain.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2009-08-10 05:46:50 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2009-08-10 05:46:50 +0000
commit9bd27b7c9e998087f390774bd0f43916813a2847 (patch)
treeee2b7fa2b2e77c53c87a53a786163dae1b0e6538 /src/backend/commands/explain.c
parent18894c401f1f5ec5af1d08a12da1f183599e8560 (diff)
downloadpostgresql-9bd27b7c9e998087f390774bd0f43916813a2847.tar.gz
postgresql-9bd27b7c9e998087f390774bd0f43916813a2847.zip
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
Diffstat (limited to 'src/backend/commands/explain.c')
-rw-r--r--src/backend/commands/explain.c1257
1 files changed, 972 insertions, 285 deletions
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1388c8dd42f..d675d8d8171 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -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.188 2009/07/26 23:34:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.189 2009/08/10 05:46:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -32,6 +32,7 @@
#include "utils/lsyscache.h"
#include "utils/tuplesort.h"
#include "utils/snapmgr.h"
+#include "utils/xml.h"
/* Hook for plugins to get control in ExplainOneQuery() */
@@ -41,28 +42,60 @@ ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
+/* OR-able flags for ExplainXMLTag() */
+#define X_OPENING 0
+#define X_CLOSING 1
+#define X_CLOSE_IMMEDIATE 2
+#define X_NOWHITESPACE 4
+
static void ExplainOneQuery(Query *query, ExplainState *es,
const char *queryString, ParamListInfo params);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
- StringInfo buf);
+ ExplainState *es);
static double elapsed_time(instr_time *starttime);
static void ExplainNode(Plan *plan, PlanState *planstate,
- Plan *outer_plan, int indent, ExplainState *es);
-static void show_plan_tlist(Plan *plan, int indent, ExplainState *es);
+ Plan *outer_plan,
+ const char *relationship, const char *plan_name,
+ ExplainState *es);
+static void show_plan_tlist(Plan *plan, ExplainState *es);
static void show_qual(List *qual, const char *qlabel, Plan *plan,
- Plan *outer_plan, int indent, bool useprefix, ExplainState *es);
+ Plan *outer_plan, bool useprefix, ExplainState *es);
static void show_scan_qual(List *qual, const char *qlabel,
Plan *scan_plan, Plan *outer_plan,
- int indent, ExplainState *es);
+ ExplainState *es);
static void show_upper_qual(List *qual, const char *qlabel, Plan *plan,
- int indent, ExplainState *es);
-static void show_sort_keys(Plan *sortplan, int indent, ExplainState *es);
-static void show_sort_info(SortState *sortstate, int indent, ExplainState *es);
+ ExplainState *es);
+static void show_sort_keys(Plan *sortplan, ExplainState *es);
+static void show_sort_info(SortState *sortstate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void ExplainScanTarget(Scan *plan, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstate,
- Plan *outer_plan, int indent, ExplainState *es);
-static void ExplainSubPlans(List *plans, int indent, ExplainState *es);
+ Plan *outer_plan, ExplainState *es);
+static void ExplainSubPlans(List *plans, const char *relationship,
+ ExplainState *es);
+static void ExplainPropertyList(const char *qlabel, List *data,
+ ExplainState *es);
+static void ExplainProperty(const char *qlabel, const char *value,
+ bool numeric, ExplainState *es);
+#define ExplainPropertyText(qlabel, value, es) \
+ ExplainProperty(qlabel, value, false, es)
+static void ExplainPropertyInteger(const char *qlabel, int value,
+ ExplainState *es);
+static void ExplainPropertyLong(const char *qlabel, long value,
+ ExplainState *es);
+static void ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
+ ExplainState *es);
+static void ExplainOpenGroup(const char *objtype, const char *labelname,
+ bool labeled, ExplainState *es);
+static void ExplainCloseGroup(const char *objtype, const char *labelname,
+ bool labeled, ExplainState *es);
+static void ExplainDummyGroup(const char *objtype, const char *labelname,
+ ExplainState *es);
+static void ExplainBeginOutput(ExplainState *es);
+static void ExplainEndOutput(ExplainState *es);
+static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
+static void ExplainJSONLineEnding(ExplainState *es);
+static void escape_json(StringInfo buf, const char *str);
/*
@@ -94,6 +127,22 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
es.verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "costs") == 0)
es.costs = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "format") == 0)
+ {
+ char *p = defGetString(opt);
+
+ if (strcmp(p, "text") == 0)
+ es.format = EXPLAIN_FORMAT_TEXT;
+ else if (strcmp(p, "xml") == 0)
+ es.format = EXPLAIN_FORMAT_XML;
+ else if (strcmp(p, "json") == 0)
+ es.format = EXPLAIN_FORMAT_JSON;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
+ opt->defname, p)));
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -117,10 +166,17 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
queryString, param_types, num_params);
+ /* emit opening boilerplate */
+ ExplainBeginOutput(&es);
+
if (rewritten == NIL)
{
- /* In the case of an INSTEAD NOTHING, tell at least that */
- appendStringInfoString(es.str, "Query rewrites to nothing\n");
+ /*
+ * In the case of an INSTEAD NOTHING, tell at least that. But in
+ * non-text format, the output is delimited, so this isn't necessary.
+ */
+ if (es.format == EXPLAIN_FORMAT_TEXT)
+ appendStringInfoString(es.str, "Query rewrites to nothing\n");
}
else
{
@@ -130,15 +186,23 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
foreach(l, rewritten)
{
ExplainOneQuery((Query *) lfirst(l), &es, queryString, params);
- /* put a blank line between plans */
+
+ /* Separate plans with an appropriate separator */
if (lnext(l) != NULL)
- appendStringInfoChar(es.str, '\n');
+ ExplainSeparatePlans(&es);
}
}
+ /* emit closing boilerplate */
+ ExplainEndOutput(&es);
+ Assert(es.indent == 0);
+
/* output tuples */
tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
- do_text_output_multiline(tstate, es.str->data);
+ if (es.format == EXPLAIN_FORMAT_TEXT)
+ do_text_output_multiline(tstate, es.str->data);
+ else
+ do_text_output_oneline(tstate, es.str->data);
end_tup_output(tstate);
pfree(es.str->data);
@@ -165,11 +229,26 @@ TupleDesc
ExplainResultDesc(ExplainStmt *stmt)
{
TupleDesc tupdesc;
+ ListCell *lc;
+ bool xml = false;
- /* need a tuple descriptor representing a single TEXT column */
+ /* Check for XML format option */
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "format") == 0)
+ {
+ char *p = defGetString(opt);
+
+ xml = (strcmp(p, "xml") == 0);
+ }
+ }
+
+ /* Need a tuple descriptor representing a single TEXT or XML column */
tupdesc = CreateTemplateTupleDesc(1, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
- TEXTOID, -1, 0);
+ xml ? XMLOID : TEXTOID, -1, 0);
return tupdesc;
}
@@ -223,10 +302,20 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es,
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es,
queryString, params);
else if (IsA(utilityStmt, NotifyStmt))
- appendStringInfoString(es->str, "NOTIFY\n");
+ {
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ appendStringInfoString(es->str, "NOTIFY\n");
+ else
+ ExplainDummyGroup("Notify", NULL, es);
+ }
else
- appendStringInfoString(es->str,
+ {
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ appendStringInfoString(es->str,
"Utility statements have no plan structure\n");
+ else
+ ExplainDummyGroup("Utility Statement", NULL, es);
+ }
}
/*
@@ -288,6 +377,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
totaltime += elapsed_time(&starttime);
}
+ ExplainOpenGroup("Query", NULL, true, es);
+
/* Create textual dump of plan tree */
ExplainPrintPlan(es, queryDesc);
@@ -313,16 +404,20 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
int nr;
ListCell *l;
+ ExplainOpenGroup("Triggers", "Triggers", false, es);
+
show_relname = (numrels > 1 || targrels != NIL);
rInfo = queryDesc->estate->es_result_relations;
for (nr = 0; nr < numrels; rInfo++, nr++)
- report_triggers(rInfo, show_relname, es->str);
+ report_triggers(rInfo, show_relname, es);
foreach(l, targrels)
{
rInfo = (ResultRelInfo *) lfirst(l);
- report_triggers(rInfo, show_relname, es->str);
+ report_triggers(rInfo, show_relname, es);
}
+
+ ExplainCloseGroup("Triggers", "Triggers", false, es);
}
/*
@@ -344,8 +439,16 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
totaltime += elapsed_time(&starttime);
if (es->analyze)
- appendStringInfo(es->str, "Total runtime: %.3f ms\n",
- 1000.0 * totaltime);
+ {
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ appendStringInfo(es->str, "Total runtime: %.3f ms\n",
+ 1000.0 * totaltime);
+ else
+ ExplainPropertyFloat("Total Runtime", 1000.0 * totaltime,
+ 3, es);
+ }
+
+ ExplainCloseGroup("Query", NULL, true, es);
}
/*
@@ -365,7 +468,7 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
es->pstmt = queryDesc->plannedstmt;
es->rtable = queryDesc->plannedstmt->rtable;
ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate,
- NULL, 0, es);
+ NULL, NULL, NULL, es);
}
/*
@@ -373,7 +476,7 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
* report execution stats for a single relation's triggers
*/
static void
-report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf)
+report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es)
{
int nt;
@@ -383,7 +486,8 @@ report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf)
{
Trigger *trig = rInfo->ri_TrigDesc->triggers + nt;
Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
- char *conname;
+ char *relname;
+ char *conname = NULL;
/* Must clean up instrumentation state */
InstrEndLoop(instr);
@@ -395,21 +499,44 @@ report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf)
if (instr->ntuples == 0)
continue;
- if (OidIsValid(trig->tgconstraint) &&
- (conname = get_constraint_name(trig->tgconstraint)) != NULL)
+ ExplainOpenGroup("Trigger", NULL, true, es);
+
+ relname = RelationGetRelationName(rInfo->ri_RelationDesc);
+ if (OidIsValid(trig->tgconstraint))
+ conname = get_constraint_name(trig->tgconstraint);
+
+ /*
+ * In text format, we avoid printing both the trigger name and the
+ * constraint name unless VERBOSE is specified. In non-text
+ * formats we just print everything.
+ */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
{
- appendStringInfo(buf, "Trigger for constraint %s", conname);
- pfree(conname);
+ if (es->verbose || conname == NULL)
+ appendStringInfo(es->str, "Trigger %s", trig->tgname);
+ else
+ appendStringInfoString(es->str, "Trigger");
+ if (conname)
+ appendStringInfo(es->str, " for constraint %s", conname);
+ if (show_relname)
+ appendStringInfo(es->str, " on %s", relname);
+ appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",
+ 1000.0 * instr->total, instr->ntuples);
}
else
- appendStringInfo(buf, "Trigger %s", trig->tgname);
+ {
+ ExplainPropertyText("Trigger Name", trig->tgname, es);
+ if (conname)
+ ExplainPropertyText("Constraint Name", conname, es);
+ ExplainPropertyText("Relation", relname, es);
+ ExplainPropertyFloat("Time", 1000.0 * instr->total, 3, es);
+ ExplainPropertyFloat("Calls", instr->ntuples, 0, es);
+ }
- if (show_relname)
- appendStringInfo(buf, " on %s",
- RelationGetRelationName(rInfo->ri_RelationDesc));
+ if (conname)
+ pfree(conname);
- appendStringInfo(buf, ": time=%.3f calls=%.0f\n",
- 1000.0 * instr->total, instr->ntuples);
+ ExplainCloseGroup("Trigger", NULL, true, es);
}
}
@@ -426,7 +553,7 @@ elapsed_time(instr_time *starttime)
/*
* ExplainNode -
- * converts a Plan node into ascii string and appends it to es->str
+ * Appends a description of the Plan node to es->str
*
* planstate points to the executor state node corresponding to the plan node.
* We need this to get at the instrumentation data (if any) as well as the
@@ -436,253 +563,222 @@ elapsed_time(instr_time *starttime)
* side of a join with the current node. This is only interesting for
* deciphering runtime keys of an inner indexscan.
*
- * If indent is positive, we indent the plan output accordingly and put "->"
- * in front of it. This should only happen for child plan nodes.
+ * relationship describes the relationship of this plan node to its parent
+ * (eg, "Outer", "Inner"); it can be null at top level. plan_name is an
+ * optional name to be attached to the node.
+ *
+ * In text format, es->indent is controlled in this function since we only
+ * want it to change at Plan-node boundaries. In non-text formats, es->indent
+ * corresponds to the nesting depth of logical output groups, and therefore
+ * is controlled by ExplainOpenGroup/ExplainCloseGroup.
*/
static void
ExplainNode(Plan *plan, PlanState *planstate,
Plan *outer_plan,
- int indent, ExplainState *es)
+ const char *relationship, const char *plan_name,
+ ExplainState *es)
{
- const char *pname;
+ const char *pname; /* node type name for text output */
+ const char *sname; /* node type name for non-text output */
+ const char *strategy = NULL;
+ int save_indent = es->indent;
+ bool haschildren;
- if (indent)
- {
- Assert(indent >= 2);
- appendStringInfoSpaces(es->str, 2 * indent - 4);
- appendStringInfoString(es->str, "-> ");
- }
-
- if (plan == NULL)
- {
- appendStringInfoChar(es->str, '\n');
- return;
- }
+ Assert(plan);
switch (nodeTag(plan))
{
case T_Result:
- pname = "Result";
+ pname = sname = "Result";
break;
case T_Append:
- pname = "Append";
+ pname = sname = "Append";
break;
case T_RecursiveUnion:
- pname = "Recursive Union";
+ pname = sname = "Recursive Union";
break;
case T_BitmapAnd:
- pname = "BitmapAnd";
+ pname = sname = "BitmapAnd";
break;
case T_BitmapOr:
- pname = "BitmapOr";
+ pname = sname = "BitmapOr";
break;
case T_NestLoop:
- switch (((NestLoop *) plan)->join.jointype)
- {
- case JOIN_INNER:
- pname = "Nested Loop";
- break;
- case JOIN_LEFT:
- pname = "Nested Loop Left Join";
- break;
- case JOIN_FULL:
- pname = "Nested Loop Full Join";
- break;
- case JOIN_RIGHT:
- pname = "Nested Loop Right Join";
- break;
- case JOIN_SEMI:
- pname = "Nested Loop Semi Join";
- break;
- case JOIN_ANTI:
- pname = "Nested Loop Anti Join";
- break;
- default:
- pname = "Nested Loop ??? Join";
- break;
- }
+ pname = sname = "Nested Loop";
break;
case T_MergeJoin:
- switch (((MergeJoin *) plan)->join.jointype)
- {
- case JOIN_INNER:
- pname = "Merge Join";
- break;
- case JOIN_LEFT:
- pname = "Merge Left Join";
- break;
- case JOIN_FULL:
- pname = "Merge Full Join";
- break;
- case JOIN_RIGHT:
- pname = "Merge Right Join";
- break;
- case JOIN_SEMI:
- pname = "Merge Semi Join";
- break;
- case JOIN_ANTI:
- pname = "Merge Anti Join";
- break;
- default:
- pname = "Merge ??? Join";
- break;
- }
+ pname = "Merge"; /* "Join" gets added by jointype switch */
+ sname = "Merge Join";
break;
case T_HashJoin:
- switch (((HashJoin *) plan)->join.jointype)
- {
- case JOIN_INNER:
- pname = "Hash Join";
- break;
- case JOIN_LEFT:
- pname = "Hash Left Join";
- break;
- case JOIN_FULL:
- pname = "Hash Full Join";
- break;
- case JOIN_RIGHT:
- pname = "Hash Right Join";
- break;
- case JOIN_SEMI:
- pname = "Hash Semi Join";
- break;
- case JOIN_ANTI:
- pname = "Hash Anti Join";
- break;
- default:
- pname = "Hash ??? Join";
- break;
- }
+ pname = "Hash"; /* "Join" gets added by jointype switch */
+ sname = "Hash Join";
break;
case T_SeqScan:
- pname = "Seq Scan";
+ pname = sname = "Seq Scan";
break;
case T_IndexScan:
- pname = "Index Scan";
+ pname = sname = "Index Scan";
break;
case T_BitmapIndexScan:
- pname = "Bitmap Index Scan";
+ pname = sname = "Bitmap Index Scan";
break;
case T_BitmapHeapScan:
- pname = "Bitmap Heap Scan";
+ pname = sname = "Bitmap Heap Scan";
break;
case T_TidScan:
- pname = "Tid Scan";
+ pname = sname = "Tid Scan";
break;
case T_SubqueryScan:
- pname = "Subquery Scan";
+ pname = sname = "Subquery Scan";
break;
case T_FunctionScan:
- pname = "Function Scan";
+ pname = sname = "Function Scan";
break;
case T_ValuesScan:
- pname = "Values Scan";
+ pname = sname = "Values Scan";
break;
case T_CteScan:
- pname = "CTE Scan";
+ pname = sname = "CTE Scan";
break;
case T_WorkTableScan:
- pname = "WorkTable Scan";
+ pname = sname = "WorkTable Scan";
break;
case T_Material:
- pname = "Materialize";
+ pname = sname = "Materialize";
break;
case T_Sort:
- pname = "Sort";
+ pname = sname = "Sort";
break;
case T_Group:
- pname = "Group";
+ pname = sname = "Group";
break;
case T_Agg:
+ sname = "Aggregate";
switch (((Agg *) plan)->aggstrategy)
{
case AGG_PLAIN:
pname = "Aggregate";
+ strategy = "Plain";
break;
case AGG_SORTED:
pname = "GroupAggregate";
+ strategy = "Sorted";
break;
case AGG_HASHED:
pname = "HashAggregate";
+ strategy = "Hashed";
break;
default:
pname = "Aggregate ???";
+ strategy = "???";
break;
}
break;
case T_WindowAgg:
- pname = "WindowAgg";
+ pname = sname = "WindowAgg";
break;
case T_Unique:
- pname = "Unique";
+ pname = sname = "Unique";
break;
case T_SetOp:
+ sname = "SetOp";
switch (((SetOp *) plan)->strategy)
{
case SETOP_SORTED:
- switch (((SetOp *) plan)->cmd)
- {
- case SETOPCMD_INTERSECT:
- pname = "SetOp Intersect";
- break;
- case SETOPCMD_INTERSECT_ALL:
- pname = "SetOp Intersect All";
- break;
- case SETOPCMD_EXCEPT:
- pname = "SetOp Except";
- break;
- case SETOPCMD_EXCEPT_ALL:
- pname = "SetOp Except All";
- break;
- default:
- pname = "SetOp ???";
- break;
- }
+ pname = "SetOp";
+ strategy = "Sorted";
break;
case SETOP_HASHED:
- switch (((SetOp *) plan)->cmd)
- {
- case SETOPCMD_INTERSECT:
- pname = "HashSetOp Intersect";
- break;
- case SETOPCMD_INTERSECT_ALL:
- pname = "HashSetOp Intersect All";
- break;
- case SETOPCMD_EXCEPT:
- pname = "HashSetOp Except";
- break;
- case SETOPCMD_EXCEPT_ALL:
- pname = "HashSetOp Except All";
- break;
- default:
- pname = "HashSetOp ???";
- break;
- }
+ pname = "HashSetOp";
+ strategy = "Hashed";
break;
default:
pname = "SetOp ???";
+ strategy = "???";
break;
}
break;
case T_Limit:
- pname = "Limit";
+ pname = sname = "Limit";
break;
case T_Hash:
- pname = "Hash";
+ pname = sname = "Hash";
break;
default:
- pname = "???";
+ pname = sname = "???";
break;
}
- appendStringInfoString(es->str, pname);
+ ExplainOpenGroup("Plan",
+ relationship ? NULL : "Plan",
+ true, es);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ if (plan_name)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfo(es->str, "%s\n", plan_name);
+ es->indent++;
+ }
+ if (es->indent)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfoString(es->str, "-> ");
+ es->indent += 2;
+ }
+ appendStringInfoString(es->str, pname);
+ es->indent++;
+ }
+ else
+ {
+ ExplainPropertyText("Node Type", sname, es);
+ if (strategy)
+ ExplainPropertyText("Strategy", strategy, es);
+ if (relationship)
+ ExplainPropertyText("Parent Relationship", relationship, es);
+ if (plan_name)
+ ExplainPropertyText("Subplan Name", plan_name, es);
+ }
+
switch (nodeTag(plan))
{
case T_IndexScan:
- if (ScanDirectionIsBackward(((IndexScan *) plan)->indexorderdir))
- appendStringInfoString(es->str, " Backward");
- appendStringInfo(es->str, " using %s",
- explain_get_index_name(((IndexScan *) plan)->indexid));
+ {
+ IndexScan *indexscan = (IndexScan *) plan;
+ const char *indexname =
+ explain_get_index_name(indexscan->indexid);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ if (ScanDirectionIsBackward(indexscan->indexorderdir))
+ appendStringInfoString(es->str, " Backward");
+ appendStringInfo(es->str, " using %s", indexname);
+ }
+ else
+ {
+ const char *scandir;
+
+ switch (indexscan->indexorderdir)
+ {
+ case BackwardScanDirection:
+ scandir = "Backward";
+ break;
+ case NoMovementScanDirection:
+ scandir = "NoMovement";
+ break;
+ case ForwardScanDirection:
+ scandir = "Forward";
+ break;
+ default:
+ scandir = "???";
+ break;
+ }
+ ExplainPropertyText("Scan Direction", scandir, es);
+ ExplainPropertyText("Index Name", indexname, es);
+ }
+ }
/* FALL THRU */
case T_SeqScan:
case T_BitmapHeapScan:
@@ -695,17 +791,110 @@ ExplainNode(Plan *plan, PlanState *planstate,
ExplainScanTarget((Scan *) plan, es);
break;
case T_BitmapIndexScan:
- appendStringInfo(es->str, " on %s",
- explain_get_index_name(((BitmapIndexScan *) plan)->indexid));
+ {
+ BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
+ const char *indexname =
+ explain_get_index_name(bitmapindexscan->indexid);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ appendStringInfo(es->str, " on %s", indexname);
+ else
+ ExplainPropertyText("Index Name", indexname, es);
+ }
+ break;
+ case T_NestLoop:
+ case T_MergeJoin:
+ case T_HashJoin:
+ {
+ const char *jointype;
+
+ switch (((Join *) plan)->jointype)
+ {
+ case JOIN_INNER:
+ jointype = "Inner";
+ break;
+ case JOIN_LEFT:
+ jointype = "Left";
+ break;
+ case JOIN_FULL:
+ jointype = "Full";
+ break;
+ case JOIN_RIGHT:
+ jointype = "Right";
+ break;
+ case JOIN_SEMI:
+ jointype = "Semi";
+ break;
+ case JOIN_ANTI:
+ jointype = "Anti";
+ break;
+ default:
+ jointype = "???";
+ break;
+ }
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ /*
+ * For historical reasons, the join type is interpolated
+ * into the node type name...
+ */
+ if (((Join *) plan)->jointype != JOIN_INNER)
+ appendStringInfo(es->str, " %s Join", jointype);
+ else if (!IsA(plan, NestLoop))
+ appendStringInfo(es->str, " Join");
+ }
+ else
+ ExplainPropertyText("Join Type", jointype, es);
+ }
+ break;
+ case T_SetOp:
+ {
+ const char *setopcmd;
+
+ switch (((SetOp *) plan)->cmd)
+ {
+ case SETOPCMD_INTERSECT:
+ setopcmd = "Intersect";
+ break;
+ case SETOPCMD_INTERSECT_ALL:
+ setopcmd = "Intersect All";
+ break;
+ case SETOPCMD_EXCEPT:
+ setopcmd = "Except";
+ break;
+ case SETOPCMD_EXCEPT_ALL:
+ setopcmd = "Except All";
+ break;
+ default:
+ setopcmd = "???";
+ break;
+ }
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ appendStringInfo(es->str, " %s", setopcmd);
+ else
+ ExplainPropertyText("Command", setopcmd, es);
+ }
break;
default:
break;
}
if (es->costs)
- appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
- plan->startup_cost, plan->total_cost,
- plan->plan_rows, plan->plan_width);
+ {
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
+ plan->startup_cost, plan->total_cost,
+ plan->plan_rows, plan->plan_width);
+ }
+ else
+ {
+ ExplainPropertyFloat("Startup Cost", plan->startup_cost, 2, es);
+ ExplainPropertyFloat("Total Cost", plan->total_cost, 2, es);
+ ExplainPropertyFloat("Plan Rows", plan->plan_rows, 0, es);
+ ExplainPropertyInteger("Plan Width", plan->plan_width, es);
+ }
+ }
/*
* We have to forcibly clean up the instrumentation state because we
@@ -717,38 +906,60 @@ ExplainNode(Plan *plan, PlanState *planstate,
if (planstate->instrument && planstate->instrument->nloops > 0)
{
double nloops = planstate->instrument->nloops;
+ double startup_sec = 1000.0 * planstate->instrument->startup / nloops;
+ double total_sec = 1000.0 * planstate->instrument->total / nloops;
+ double rows = planstate->instrument->ntuples / nloops;
- appendStringInfo(es->str,
- " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
- 1000.0 * planstate->instrument->startup / nloops,
- 1000.0 * planstate->instrument->total / nloops,
- planstate->instrument->ntuples / nloops,
- planstate->instrument->nloops);
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfo(es->str,
+ " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
+ startup_sec, total_sec, rows, nloops);
+ }
+ else
+ {
+ ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
+ ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
+ ExplainPropertyFloat("Actual Rows", rows, 0, es);
+ ExplainPropertyFloat("Actual Loops", nloops, 0, es);
+ }
}
else if (es->analyze)
- appendStringInfoString(es->str, " (never executed)");
- appendStringInfoChar(es->str, '\n');
+ {
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ appendStringInfo(es->str, " (never executed)");
+ else
+ {
+ ExplainPropertyFloat("Actual Startup Time", 0.0, 3, es);
+ ExplainPropertyFloat("Actual Total Time", 0.0, 3, es);
+ ExplainPropertyFloat("Actual Rows", 0.0, 0, es);
+ ExplainPropertyFloat("Actual Loops", 0.0, 0, es);
+ }
+ }
+
+ /* in text format, first line ends here */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ appendStringInfoChar(es->str, '\n');
/* target list */
if (es->verbose)
- show_plan_tlist(plan, indent, es);
+ show_plan_tlist(plan, es);
/* quals, sort keys, etc */
switch (nodeTag(plan))
{
case T_IndexScan:
show_scan_qual(((IndexScan *) plan)->indexqualorig,
- "Index Cond", plan, outer_plan, indent, es);
- show_scan_qual(plan->qual,
- "Filter", plan, outer_plan, indent, es);
+ "Index Cond", plan, outer_plan, es);
+ show_scan_qual(plan->qual, "Filter", plan, outer_plan, es);
break;
case T_BitmapIndexScan:
show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
- "Index Cond", plan, outer_plan, indent, es);
+ "Index Cond", plan, outer_plan, es);
break;
case T_BitmapHeapScan:
show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
- "Recheck Cond", plan, outer_plan, indent, es);
+ "Recheck Cond", plan, outer_plan, es);
/* FALL THRU */
case T_SeqScan:
case T_FunctionScan:
@@ -756,8 +967,7 @@ ExplainNode(Plan *plan, PlanState *planstate,
case T_CteScan:
case T_WorkTableScan:
case T_SubqueryScan:
- show_scan_qual(plan->qual,
- "Filter", plan, outer_plan, indent, es);
+ show_scan_qual(plan->qual, "Filter", plan, outer_plan, es);
break;
case T_TidScan:
{
@@ -769,51 +979,61 @@ ExplainNode(Plan *plan, PlanState *planstate,
if (list_length(tidquals) > 1)
tidquals = list_make1(make_orclause(tidquals));
- show_scan_qual(tidquals,
- "TID Cond", plan, outer_plan, indent, es);
- show_scan_qual(plan->qual,
- "Filter", plan, outer_plan, indent, es);
+ show_scan_qual(tidquals, "TID Cond", plan, outer_plan, es);
+ show_scan_qual(plan->qual, "Filter", plan, outer_plan, es);
}
break;
case T_NestLoop:
show_upper_qual(((NestLoop *) plan)->join.joinqual,
- "Join Filter", plan, indent, es);
- show_upper_qual(plan->qual, "Filter", plan, indent, es);
+ "Join Filter", plan, es);
+ show_upper_qual(plan->qual, "Filter", plan, es);
break;
case T_MergeJoin:
show_upper_qual(((MergeJoin *) plan)->mergeclauses,
- "Merge Cond", plan, indent, es);
+ "Merge Cond", plan, es);
show_upper_qual(((MergeJoin *) plan)->join.joinqual,
- "Join Filter", plan, indent, es);
- show_upper_qual(plan->qual, "Filter", plan, indent, es);
+ "Join Filter", plan, es);
+ show_upper_qual(plan->qual, "Filter", plan, es);
break;
case T_HashJoin:
show_upper_qual(((HashJoin *) plan)->hashclauses,
- "Hash Cond", plan, indent, es);
+ "Hash Cond", plan, es);
show_upper_qual(((HashJoin *) plan)->join.joinqual,
- "Join Filter", plan, indent, es);
- show_upper_qual(plan->qual, "Filter", plan, indent, es);
+ "Join Filter", plan, es);
+ show_upper_qual(plan->qual, "Filter", plan, es);
break;
case T_Agg:
case T_Group:
- show_upper_qual(plan->qual, "Filter", plan, indent, es);
+ show_upper_qual(plan->qual, "Filter", plan, es);
break;
case T_Sort:
- show_sort_keys(plan, indent, es);
- show_sort_info((SortState *) planstate, indent, es);
+ show_sort_keys(plan, es);
+ show_sort_info((SortState *) planstate, es);
break;
case T_Result:
show_upper_qual((List *) ((Result *) plan)->resconstantqual,
- "One-Time Filter", plan, indent, es);
- show_upper_qual(plan->qual, "Filter", plan, indent, es);
+ "One-Time Filter", plan, es);
+ show_upper_qual(plan->qual, "Filter", plan, es);
break;
default:
break;
}
+ /* Get ready to display the child plans */
+ haschildren = plan->initPlan ||
+ outerPlan(plan) ||
+ innerPlan(plan) ||
+ IsA(plan, Append) ||
+ IsA(plan, BitmapAnd) ||
+ IsA(plan, BitmapOr) ||
+ IsA(plan, SubqueryScan) ||
+ planstate->subPlan;
+ if (haschildren)
+ ExplainOpenGroup("Plans", "Plans", false, es);
+
/* initPlan-s */
if (plan->initPlan)
- ExplainSubPlans(planstate->initPlan, indent, es);
+ ExplainSubPlans(planstate->initPlan, "InitPlan", es);
/* lefttree */
if (outerPlan(plan))
@@ -825,14 +1045,15 @@ ExplainNode(Plan *plan, PlanState *planstate,
*/
ExplainNode(outerPlan(plan), outerPlanState(planstate),
IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
- indent + 3, es);
+ "Outer", NULL, es);
}
/* righttree */
if (innerPlan(plan))
{
ExplainNode(innerPlan(plan), innerPlanState(planstate),
- outerPlan(plan), indent + 3, es);
+ outerPlan(plan),
+ "Inner", NULL, es);
}
/* special child plans */
@@ -841,17 +1062,17 @@ ExplainNode(Plan *plan, PlanState *planstate,
case T_Append:
ExplainMemberNodes(((Append *) plan)->appendplans,
((AppendState *) planstate)->appendplans,
- outer_plan, indent, es);
+ outer_plan, es);
break;
case T_BitmapAnd:
ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
((BitmapAndState *) planstate)->bitmapplans,
- outer_plan, indent, es);
+ outer_plan, es);
break;
case T_BitmapOr:
ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
((BitmapOrState *) planstate)->bitmapplans,
- outer_plan, indent, es);
+ outer_plan, es);
break;
case T_SubqueryScan:
{
@@ -859,7 +1080,8 @@ ExplainNode(Plan *plan, PlanState *planstate,
SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
ExplainNode(subqueryscan->subplan, subquerystate->subplan,
- NULL, indent + 3, es);
+ NULL,
+ "Subquery", NULL, es);
}
break;
default:
@@ -868,16 +1090,29 @@ ExplainNode(Plan *plan, PlanState *planstate,
/* subPlan-s */
if (planstate->subPlan)
- ExplainSubPlans(planstate->subPlan, indent, es);
+ ExplainSubPlans(planstate->subPlan, "SubPlan", es);
+
+ /* end of child plans */
+ if (haschildren)
+ ExplainCloseGroup("Plans", "Plans", false, es);
+
+ /* in text format, undo whatever indentation we added */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ es->indent = save_indent;
+
+ ExplainCloseGroup("Plan",
+ relationship ? NULL : "Plan",
+ true, es);
}
/*
* Show the targetlist of a plan node
*/
static void
-show_plan_tlist(Plan *plan, int indent, ExplainState *es)
+show_plan_tlist(Plan *plan, ExplainState *es)
{
List *context;
+ List *result = NIL;
bool useprefix;
ListCell *lc;
int i;
@@ -899,10 +1134,6 @@ show_plan_tlist(Plan *plan, int indent, ExplainState *es)
es->pstmt->subplans);
useprefix = list_length(es->rtable) > 1;
- /* Emit line prefix */
- appendStringInfoSpaces(es->str, indent * 2);
- appendStringInfoString(es->str, " Output: ");
-
/* Deparse each non-junk result column */
i = 0;
foreach(lc, plan->targetlist)
@@ -911,14 +1142,13 @@ show_plan_tlist(Plan *plan, int indent, ExplainState *es)
if (tle->resjunk)
continue;
- if (i++ > 0)
- appendStringInfoString(es->str, ", ");
- appendStringInfoString(es->str,
- deparse_expression((Node *) tle->expr, context,
+ result = lappend(result,
+ deparse_expression((Node *) tle->expr, context,
useprefix, false));
}
- appendStringInfoChar(es->str, '\n');
+ /* Print results */
+ ExplainPropertyList("Output", result, es);
}
/*
@@ -929,7 +1159,7 @@ show_plan_tlist(Plan *plan, int indent, ExplainState *es)
*/
static void
show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan,
- int indent, bool useprefix, ExplainState *es)
+ bool useprefix, ExplainState *es)
{
List *context;
Node *node;
@@ -952,8 +1182,7 @@ show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan,
exprstr = deparse_expression(node, context, useprefix, false);
/* And add to es->str */
- appendStringInfoSpaces(es->str, indent * 2);
- appendStringInfo(es->str, " %s: %s\n", qlabel, exprstr);
+ ExplainPropertyText(qlabel, exprstr, es);
}
/*
@@ -962,36 +1191,37 @@ show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan,
static void
show_scan_qual(List *qual, const char *qlabel,
Plan *scan_plan, Plan *outer_plan,
- int indent, ExplainState *es)
+ ExplainState *es)
{
bool useprefix;
- useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan));
- show_qual(qual, qlabel, scan_plan, outer_plan, indent, useprefix, es);
+ useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan) ||
+ es->verbose);
+ show_qual(qual, qlabel, scan_plan, outer_plan, useprefix, es);
}
/*
* Show a qualifier expression for an upper-level plan node
*/
static void
-show_upper_qual(List *qual, const char *qlabel, Plan *plan,
- int indent, ExplainState *es)
+show_upper_qual(List *qual, const char *qlabel, Plan *plan, ExplainState *es)
{
bool useprefix;
- useprefix = (list_length(es->rtable) > 1);
- show_qual(qual, qlabel, plan, NULL, indent, useprefix, es);
+ useprefix = (list_length(es->rtable) > 1 || es->verbose);
+ show_qual(qual, qlabel, plan, NULL, useprefix, es);
}
/*
* Show the sort keys for a Sort node.
*/
static void
-show_sort_keys(Plan *sortplan, int indent, ExplainState *es)
+show_sort_keys(Plan *sortplan, ExplainState *es)
{
int nkeys = ((Sort *) sortplan)->numCols;
AttrNumber *keycols = ((Sort *) sortplan)->sortColIdx;
List *context;
+ List *result = NIL;
bool useprefix;
int keyno;
char *exprstr;
@@ -999,15 +1229,12 @@ show_sort_keys(Plan *sortplan, int indent, ExplainState *es)
if (nkeys <= 0)
return;
- appendStringInfoSpaces(es->str, indent * 2);
- appendStringInfoString(es->str, " Sort Key: ");
-
/* Set up deparsing context */
context = deparse_context_for_plan((Node *) sortplan,
NULL,
es->rtable,
es->pstmt->subplans);
- useprefix = list_length(es->rtable) > 1;
+ useprefix = (list_length(es->rtable) > 1 || es->verbose);
for (keyno = 0; keyno < nkeys; keyno++)
{
@@ -1020,31 +1247,41 @@ show_sort_keys(Plan *sortplan, int indent, ExplainState *es)
/* Deparse the expression, showing any top-level cast */
exprstr = deparse_expression((Node *) target->expr, context,
useprefix, true);
- /* And add to es->str */
- if (keyno > 0)
- appendStringInfoString(es->str, ", ");
- appendStringInfoString(es->str, exprstr);
+ result = lappend(result, exprstr);
}
- appendStringInfoChar(es->str, '\n');
+ ExplainPropertyList("Sort Key", result, es);
}
/*
- * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node
+ * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
*/
static void
-show_sort_info(SortState *sortstate, int indent, ExplainState *es)
+show_sort_info(SortState *sortstate, ExplainState *es)
{
Assert(IsA(sortstate, SortState));
if (es->analyze && sortstate->sort_Done &&
sortstate->tuplesortstate != NULL)
{
- char *sortinfo;
+ Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;
+ const char *sortMethod;
+ const char *spaceType;
+ long spaceUsed;
- sortinfo = tuplesort_explain((Tuplesortstate *) sortstate->tuplesortstate);
- appendStringInfoSpaces(es->str, indent * 2);
- appendStringInfo(es->str, " %s\n", sortinfo);
- pfree(sortinfo);
+ tuplesort_get_stats(state, &sortMethod, &spaceType, &spaceUsed);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfo(es->str, "Sort Method: %s %s: %ldkB\n",
+ sortMethod, spaceType, spaceUsed);
+ }
+ else
+ {
+ ExplainPropertyText("Sort Method", sortMethod, es);
+ ExplainPropertyLong("Sort Space Used", spaceUsed, es);
+ ExplainPropertyText("Sort Space Type", spaceType, es);
+ }
}
}
@@ -1081,6 +1318,8 @@ static void
ExplainScanTarget(Scan *plan, ExplainState *es)
{
char *objectname = NULL;
+ char *namespace = NULL;
+ const char *objecttag = NULL;
RangeTblEntry *rte;
if (plan->scanrelid <= 0) /* Is this still possible? */
@@ -1096,6 +1335,9 @@ ExplainScanTarget(Scan *plan, ExplainState *es)
/* Assert it's on a real relation */
Assert(rte->rtekind == RTE_RELATION);
objectname = get_rel_name(rte->relid);
+ if (es->verbose)
+ namespace = get_namespace_name(get_rel_namespace(rte->relid));
+ objecttag = "Relation Name";
break;
case T_FunctionScan:
{
@@ -1116,7 +1358,11 @@ ExplainScanTarget(Scan *plan, ExplainState *es)
Oid funcid = ((FuncExpr *) funcexpr)->funcid;
objectname = get_func_name(funcid);
+ if (es->verbose)
+ namespace =
+ get_namespace_name(get_func_namespace(funcid));
}
+ objecttag = "Function Name";
}
break;
case T_ValuesScan:
@@ -1127,23 +1373,40 @@ ExplainScanTarget(Scan *plan, ExplainState *es)
Assert(rte->rtekind == RTE_CTE);
Assert(!rte->self_reference);
objectname = rte->ctename;
+ objecttag = "CTE Name";
break;
case T_WorkTableScan:
/* Assert it's on a self-reference CTE */
Assert(rte->rtekind == RTE_CTE);
Assert(rte->self_reference);
objectname = rte->ctename;
+ objecttag = "CTE Name";
break;
default:
break;
}
- appendStringInfoString(es->str, " on");
- if (objectname != NULL)
- appendStringInfo(es->str, " %s", quote_identifier(objectname));
- if (objectname == NULL || strcmp(rte->eref->aliasname, objectname) != 0)
- appendStringInfo(es->str, " %s",
- quote_identifier(rte->eref->aliasname));
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoString(es->str, " on");
+ if (namespace != NULL)
+ appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+ quote_identifier(objectname));
+ else if (objectname != NULL)
+ appendStringInfo(es->str, " %s", quote_identifier(objectname));
+ if (objectname == NULL ||
+ strcmp(rte->eref->aliasname, objectname) != 0)
+ appendStringInfo(es->str, " %s",
+ quote_identifier(rte->eref->aliasname));
+ }
+ else
+ {
+ if (objecttag != NULL && objectname != NULL)
+ ExplainPropertyText(objecttag, objectname, es);
+ if (namespace != NULL)
+ ExplainPropertyText("Schema", namespace, es);
+ ExplainPropertyText("Alias", rte->eref->aliasname, es);
+ }
}
/*
@@ -1155,7 +1418,7 @@ ExplainScanTarget(Scan *plan, ExplainState *es)
*/
static void
ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
- int indent, ExplainState *es)
+ ExplainState *es)
{
ListCell *lst;
int j = 0;
@@ -1165,7 +1428,9 @@ ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
Plan *subnode = (Plan *) lfirst(lst);
ExplainNode(subnode, planstate[j],
- outer_plan, indent + 3, es);
+ outer_plan,
+ "Member", NULL,
+ es);
j++;
}
}
@@ -1174,7 +1439,7 @@ ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
* Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).
*/
static void
-ExplainSubPlans(List *plans, int indent, ExplainState *es)
+ExplainSubPlans(List *plans, const char *relationship, ExplainState *es)
{
ListCell *lst;
@@ -1183,9 +1448,431 @@ ExplainSubPlans(List *plans, int indent, ExplainState *es)
SubPlanState *sps = (SubPlanState *) lfirst(lst);
SubPlan *sp = (SubPlan *) sps->xprstate.expr;
- appendStringInfoSpaces(es->str, indent * 2);
- appendStringInfo(es->str, " %s\n", sp->plan_name);
ExplainNode(exec_subplan_get_plan(es->pstmt, sp),
- sps->planstate, NULL, indent + 4, es);
+ sps->planstate,
+ NULL,
+ relationship, sp->plan_name,
+ es);
+ }
+}
+
+/*
+ * 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;
+ }
+}
+
+/*
+ * Explain a simple property.
+ *
+ * If "numeric" is true, the value is a number (or other value that
+ * doesn't need quoting in JSON).
+ *
+ * This usually should not be invoked directly, but via one of the datatype
+ * specific routines ExplainPropertyText, ExplainPropertyInteger, etc.
+ */
+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);
+ es->indent++;
+ 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 format, 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);
+ es->indent++;
+ break;
+ }
+}
+
+/*
+ * 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)
+{
+ switch (es->format)
+ {
+ case EXPLAIN_FORMAT_TEXT:
+ /* nothing to do */
+ break;
+
+ case EXPLAIN_FORMAT_XML:
+ es->indent--;
+ ExplainXMLTag(objtype, X_CLOSING, es);
+ break;
+
+ case EXPLAIN_FORMAT_JSON:
+ es->indent--;
+ 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 a "dummy" group that never has any members.
+ *
+ * objtype is the type of the group object, labelname is its label within
+ * a containing object (if any).
+ */
+static void
+ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
+{
+ switch (es->format)
+ {
+ case EXPLAIN_FORMAT_TEXT:
+ /* nothing to do */
+ break;
+
+ case EXPLAIN_FORMAT_XML:
+ ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es);
+ break;
+
+ case EXPLAIN_FORMAT_JSON:
+ ExplainJSONLineEnding(es);
+ appendStringInfoSpaces(es->str, 2 * es->indent);
+ if (labelname)
+ {
+ escape_json(es->str, labelname);
+ appendStringInfoString(es->str, ": ");
+ }
+ escape_json(es->str, objtype);
+ break;
+ }
+}
+
+/*
+ * Emit the start-of-output boilerplate.
+ *
+ * This is just enough different from processing a subgroup that we need
+ * a separate pair of subroutines.
+ */
+static void
+ExplainBeginOutput(ExplainState *es)
+{
+ switch (es->format)
+ {
+ case EXPLAIN_FORMAT_TEXT:
+ /* nothing to do */
+ break;
+
+ case EXPLAIN_FORMAT_XML:
+ appendStringInfoString(es->str,
+ "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
+ es->indent++;
+ break;
+
+ case EXPLAIN_FORMAT_JSON:
+ /* top-level structure is an array of plans */
+ appendStringInfoChar(es->str, '[');
+ es->grouping_stack = lcons_int(0, es->grouping_stack);
+ es->indent++;
+ break;
+ }
+}
+
+/*
+ * Emit the end-of-output boilerplate.
+ */
+static void
+ExplainEndOutput(ExplainState *es)
+{
+ switch (es->format)
+ {
+ case EXPLAIN_FORMAT_TEXT:
+ /* nothing to do */
+ break;
+
+ case EXPLAIN_FORMAT_XML:
+ es->indent--;
+ appendStringInfoString(es->str, "</explain>");
+ break;
+
+ case EXPLAIN_FORMAT_JSON:
+ es->indent--;
+ appendStringInfoString(es->str, "\n]");
+ es->grouping_stack = list_delete_first(es->grouping_stack);
+ break;
+ }
+}
+
+/*
+ * Put an appropriate separator between multiple plans
+ */
+void
+ExplainSeparatePlans(ExplainState *es)
+{
+ switch (es->format)
+ {
+ case EXPLAIN_FORMAT_TEXT:
+ /* add a blank line */
+ appendStringInfoChar(es->str, '\n');
+ break;
+
+ case EXPLAIN_FORMAT_XML:
+ /* nothing to do */
+ break;
+
+ case EXPLAIN_FORMAT_JSON:
+ /* must have a comma between array elements */
+ appendStringInfoChar(es->str, ',');
+ break;
+ }
+}
+
+/*
+ * Emit opening or closing XML tag.
+ *
+ * "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE.
+ * Optionally, OR in 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);
+ if ((flags & X_CLOSE_IMMEDIATE) != 0)
+ appendStringInfoString(es->str, " /");
+ 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 format, 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, '\"');
}