machine-readable explain output

Started by Robert Haasover 16 years ago65 messages
#1Robert Haas
robertmhaas@gmail.com
2 attachment(s)

Here we go, XML and JSON output.

You will need to apply explain_refactor-v4.patch and
explain_options-v2.patch first, then apply the two patches attached to
this message.

http://archives.postgresql.org/pgsql-hackers/2009-06/msg00865.php
http://archives.postgresql.org/pgsql-hackers/2009-06/msg00866.php

The infrastructure patch applies first and is separated only for ease
of reviewing. If the infrastructure patch applied by itself changes
any user-visible behavior, it's a bug. The main patch does all the
heavy lifting. The syntax is:

explain (format xml) ...
explain (format json, analyze) ...
explain (format text) ... -- same as just plain old explain

If you don't like the syntax, please argue about that on the "generic
explain options v2" thread. Let's try to use this thread to discuss
the output format, about which I spent a good deal of time agonizing.
I felt that it was important to keep the XML and JSON output as
similar to each other as possible. This has the fairly obvious
advantage of reducing code complexity and the somewhat less obvious
advantage of avoiding expressing information in ways that are overly
tied to the syntax of XML. I think the latter is actually a pretty
important point; it's hard to know that you've done something that's
actually generic unless you actually go through the exercise of making
it apply to two different cases. This code is obviously not
completely generic; I did consider the idea that Greg Stark proposed
of having some kind of ephemeral internal format with multiple output
converters, but I couldn't figure out a way to make it work. Still,
I've made a fairly determined effort to minimize the number of places
where we switch on the output format. It's still larger than I'd
like, but I don't have any good ideas for trimming it down further.

There is an argument to be made that machine-readable output formats
shouldn't be afraid to print information that isn't displayed in the
regular output, but I haven't gone very far down that road in this
patch. I lean toward the view that any additional information that
someone wants to have in the machine-readable format should also be an
available option for the text format, because I think the question of
WHAT you want to display and HOW you want to display it are largely
orthogonal (hence options are a separate patch, and this patch just
uses that infrastructure to implement an option for format). But
there may be some exceptions. At any rate, if it's possible, I would
like to get at least some of this work committed before I go too much
further with it, since this patch stack is already four layers deep
and my head may explode if it gets too much deeper.

If it's helpful to have any of these patches further decomposed for
reviewing purposes, see here, where they are broken out into
individual commits:

http://git.postgresql.org/gitweb?p=postgresql-rhaas.git;a=shortlog;h=refs/heads/explain_format

(It's probably a bad idea to clone this repository as I am updating
the patch set by rebasing, but it's useful for browsing.)

Comments appreciated...

...Robert

Attachments:

explain_format_infrastructure-v1.patchtext/x-diff; charset=US-ASCII; name=explain_format_infrastructure-v1.patchDownload
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 43,48 **** explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
--- 43,49 ----
  typedef struct ExplainState
  {
  	StringInfo	str;			/* output buffer */
+ 	int			indent;			/* indentation level */
  	/* options */
  	bool		printTList;		/* print plan targetlists */
  	bool		printAnalyze;	/* print actual times */
***************
*** 53,79 **** typedef struct ExplainState
  
  static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
  				const char *queryString,
! 				ParamListInfo params, TupOutputState *tstate);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
  				StringInfo buf);
  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);
  static void show_qual(List *qual, const char *qlabel, Plan *plan,
! 			   Plan *outer_plan, int indent, bool useprefix, ExplainState *es);
  static void show_scan_qual(List *qual, const char *qlabel,
! 			   Plan *scan_plan, Plan *outer_plan,
! 			   int indent, 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);
  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 ExplainSubNodes(List *plans, int indent, ExplainState *es);
  
  
  /*
--- 54,79 ----
  
  static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
  				const char *queryString,
! 				ParamListInfo params, StringInfo str);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
  				StringInfo buf);
  static double elapsed_time(instr_time *starttime);
  static void ExplainNode(Plan *plan, PlanState *planstate,
! 				Plan *outer_plan, char *qlabel, 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, bool useprefix, ExplainState *es);
  static void show_scan_qual(List *qual, const char *qlabel,
! 			   Plan *scan_plan, Plan *outer_plan, ExplainState *es);
  static void show_upper_qual(List *qual, const char *qlabel, Plan *plan,
! 				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, ExplainState *es);
! static void ExplainSubNodes(List *plans, ExplainState *es);
  
  
  /*
***************
*** 89,94 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
--- 89,95 ----
  	TupOutputState *tstate;
  	List	   *rewritten;
  	ListCell   *l;
+ 	StringInfoData buf;
  
  	/* Convert parameter type data to the form parser wants */
  	getParamListTypes(params, &param_types, &num_params);
***************
*** 106,118 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
  	rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
  									   queryString, param_types, num_params);
  
! 	/* prepare for projection of tuples */
! 	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
  
  	if (rewritten == NIL)
  	{
  		/* In the case of an INSTEAD NOTHING, tell at least that */
! 		do_text_output_oneline(tstate, "Query rewrites to nothing");
  	}
  	else
  	{
--- 107,119 ----
  	rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
  									   queryString, param_types, num_params);
  
! 	/* initialize output buffer */
! 	initStringInfo(&buf);
  
  	if (rewritten == NIL)
  	{
  		/* In the case of an INSTEAD NOTHING, tell at least that */
! 		appendStringInfoString(&buf, "Query rewrites to nothing\n");
  	}
  	else
  	{
***************
*** 120,133 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
  		foreach(l, rewritten)
  		{
  			ExplainOneQuery((Query *) lfirst(l), stmt,
! 							queryString, params, tstate);
  			/* put a blank line between plans */
  			if (lnext(l) != NULL)
! 				do_text_output_oneline(tstate, "");
  		}
  	}
  
  	end_tup_output(tstate);
  }
  
  /*
--- 121,138 ----
  		foreach(l, rewritten)
  		{
  			ExplainOneQuery((Query *) lfirst(l), stmt,
! 							queryString, params, &buf);
  			/* put a blank line between plans */
  			if (lnext(l) != NULL)
! 				appendStringInfoString(&buf, "\n");
  		}
  	}
  
+ 	/* output tuples */
+ 	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
+ 	do_text_output_multiline(tstate, buf.data);
  	end_tup_output(tstate);
+ 	pfree(buf.data);
  }
  
  /*
***************
*** 152,170 **** ExplainResultDesc(ExplainStmt *stmt)
   */
  static void
  ExplainOneQuery(Query *query, ExplainStmt *stmt, const char *queryString,
! 				ParamListInfo params, TupOutputState *tstate)
  {
  	/* planner will not cope with utility statements */
  	if (query->commandType == CMD_UTILITY)
  	{
  		ExplainOneUtility(query->utilityStmt, stmt,
! 						  queryString, params, tstate);
  		return;
  	}
  
  	/* if an advisor plugin is present, let it manage things */
  	if (ExplainOneQuery_hook)
! 		(*ExplainOneQuery_hook) (query, stmt, queryString, params, tstate);
  	else
  	{
  		PlannedStmt *plan;
--- 157,175 ----
   */
  static void
  ExplainOneQuery(Query *query, ExplainStmt *stmt, const char *queryString,
! 				ParamListInfo params, StringInfo str)
  {
  	/* planner will not cope with utility statements */
  	if (query->commandType == CMD_UTILITY)
  	{
  		ExplainOneUtility(query->utilityStmt, stmt,
! 						  queryString, params, str);
  		return;
  	}
  
  	/* if an advisor plugin is present, let it manage things */
  	if (ExplainOneQuery_hook)
! 		(*ExplainOneQuery_hook) (query, stmt, queryString, params, str);
  	else
  	{
  		PlannedStmt *plan;
***************
*** 173,179 **** ExplainOneQuery(Query *query, ExplainStmt *stmt, const char *queryString,
  		plan = pg_plan_query(query, 0, params);
  
  		/* run it (if needed) and produce output */
! 		ExplainOnePlan(plan, stmt, queryString, params, tstate);
  	}
  }
  
--- 178,184 ----
  		plan = pg_plan_query(query, 0, params);
  
  		/* run it (if needed) and produce output */
! 		ExplainOnePlan(plan, stmt, queryString, params, str);
  	}
  }
  
***************
*** 189,207 **** ExplainOneQuery(Query *query, ExplainStmt *stmt, const char *queryString,
  void
  ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
  				  const char *queryString, ParamListInfo params,
! 				  TupOutputState *tstate)
  {
  	if (utilityStmt == NULL)
  		return;
  
  	if (IsA(utilityStmt, ExecuteStmt))
  		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
! 							queryString, params, tstate);
  	else if (IsA(utilityStmt, NotifyStmt))
! 		do_text_output_oneline(tstate, "NOTIFY");
  	else
! 		do_text_output_oneline(tstate,
! 							   "Utility statements have no plan structure");
  }
  
  /*
--- 194,212 ----
  void
  ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
  				  const char *queryString, ParamListInfo params,
! 				  StringInfo str)
  {
  	if (utilityStmt == NULL)
  		return;
  
  	if (IsA(utilityStmt, ExecuteStmt))
  		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
! 							queryString, params, str);
  	else if (IsA(utilityStmt, NotifyStmt))
! 		appendStringInfoString(str, "NOTIFY\n");
  	else
! 		appendStringInfoString(str,
! 							   "Utility statements have no plan structure\n");
  }
  
  /*
***************
*** 221,232 **** ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
  void
  ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  			   const char *queryString, ParamListInfo params,
! 			   TupOutputState *tstate)
  {
  	QueryDesc  *queryDesc;
  	instr_time	starttime;
  	double		totaltime = 0;
- 	StringInfoData buf;
  	int			eflags;
  
  	/*
--- 226,236 ----
  void
  ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  			   const char *queryString, ParamListInfo params,
! 			   StringInfo str)
  {
  	QueryDesc  *queryDesc;
  	instr_time	starttime;
  	double		totaltime = 0;
  	int			eflags;
  
  	/*
***************
*** 267,274 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  	}
  
  	/* Create textual dump of plan tree */
! 	initStringInfo(&buf);
! 	ExplainPrintPlan(&buf, queryDesc, stmt->analyze, stmt->verbose);
  
  	/*
  	 * If we ran the command, run any AFTER triggers it queued.  (Note this
--- 271,277 ----
  	}
  
  	/* Create textual dump of plan tree */
! 	ExplainPrintPlan(str, queryDesc, stmt->analyze, stmt->verbose);
  
  	/*
  	 * If we ran the command, run any AFTER triggers it queued.  (Note this
***************
*** 295,306 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  		show_relname = (numrels > 1 || targrels != NIL);
  		rInfo = queryDesc->estate->es_result_relations;
  		for (nr = 0; nr < numrels; rInfo++, nr++)
! 			report_triggers(rInfo, show_relname, &buf);
  
  		foreach(l, targrels)
  		{
  			rInfo = (ResultRelInfo *) lfirst(l);
! 			report_triggers(rInfo, show_relname, &buf);
  		}
  	}
  
--- 298,309 ----
  		show_relname = (numrels > 1 || targrels != NIL);
  		rInfo = queryDesc->estate->es_result_relations;
  		for (nr = 0; nr < numrels; rInfo++, nr++)
! 			report_triggers(rInfo, show_relname, str);
  
  		foreach(l, targrels)
  		{
  			rInfo = (ResultRelInfo *) lfirst(l);
! 			report_triggers(rInfo, show_relname, str);
  		}
  	}
  
***************
*** 323,333 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  	totaltime += elapsed_time(&starttime);
  
  	if (stmt->analyze)
! 		appendStringInfo(&buf, "Total runtime: %.3f ms\n",
  						 1000.0 * totaltime);
- 	do_text_output_multiline(tstate, buf.data);
- 
- 	pfree(buf.data);
  }
  
  /*
--- 326,333 ----
  	totaltime += elapsed_time(&starttime);
  
  	if (stmt->analyze)
! 		appendStringInfo(str, "Total runtime: %.3f ms\n",
  						 1000.0 * totaltime);
  }
  
  /*
***************
*** 355,361 **** ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
  	es.rtable = queryDesc->plannedstmt->rtable;
  
  	ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate,
! 				NULL, 0, &es);
  }
  
  /*
--- 355,361 ----
  	es.rtable = queryDesc->plannedstmt->rtable;
  
  	ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate,
! 				NULL, NULL, &es);
  }
  
  /*
***************
*** 428,441 **** elapsed_time(instr_time *starttime)
   */
  static void
  ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
! 			int indent, ExplainState *es)
  {
  	const char *pname;
  
! 	if (indent)
  	{
! 		Assert(indent >= 2);
! 		appendStringInfoSpaces(es->str, 2 * indent - 4);
  		appendStringInfoString(es->str, "->  ");
  	}
  
--- 428,450 ----
   */
  static void
  ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
! 			char *qlabel, ExplainState *es)
  {
  	const char *pname;
+ 	int previous_indent = es->indent;
+ 
+ 	if (qlabel)
+ 	{
+ 		Assert(es->indent >= 3);
+ 		++es->indent;
+ 		appendStringInfoSpaces(es->str, es->indent * 2 - 6);
+ 		appendStringInfo(es->str, "%s\n", qlabel);
+ 	}
  
! 	if (es->indent)
  	{
! 		Assert(es->indent >= 2);
! 		appendStringInfoSpaces(es->str, 2 * es->indent - 4);
  		appendStringInfoString(es->str, "->  ");
  	}
  
***************
*** 716,740 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  
  	/* target list */
  	if (es->printTList)
! 		show_plan_tlist(plan, indent, 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);
  			break;
  		case T_BitmapIndexScan:
  			show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
! 						   "Index Cond", plan, outer_plan, indent, es);
  			break;
  		case T_BitmapHeapScan:
  			/* XXX do we want to show this in production? */
  			show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
! 						   "Recheck Cond", plan, outer_plan, indent, es);
  			/* FALL THRU */
  		case T_SeqScan:
  		case T_FunctionScan:
--- 725,748 ----
  
  	/* target list */
  	if (es->printTList)
! 		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, 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, es);
  			break;
  		case T_BitmapHeapScan:
  			/* XXX do we want to show this in production? */
  			show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
! 						   "Recheck Cond", plan, outer_plan, es);
  			/* FALL THRU */
  		case T_SeqScan:
  		case T_FunctionScan:
***************
*** 742,749 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  		case T_CteScan:
  		case T_WorkTableScan:
  		case T_SubqueryScan:
! 			show_scan_qual(plan->qual,
! 						   "Filter", plan, outer_plan, indent, es);
  			break;
  		case T_TidScan:
  			{
--- 750,756 ----
  		case T_CteScan:
  		case T_WorkTableScan:
  		case T_SubqueryScan:
! 			show_scan_qual(plan->qual, "Filter", plan, outer_plan, es);
  			break;
  		case T_TidScan:
  			{
***************
*** 755,805 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  
  				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);
  			}
  			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);
  			break;
  		case T_MergeJoin:
  			show_upper_qual(((MergeJoin *) plan)->mergeclauses,
! 							"Merge Cond", plan, indent, es);
  			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
! 							"Join Filter", plan, indent, es);
! 			show_upper_qual(plan->qual, "Filter", plan, indent, es);
  			break;
  		case T_HashJoin:
  			show_upper_qual(((HashJoin *) plan)->hashclauses,
! 							"Hash Cond", plan, indent, es);
  			show_upper_qual(((HashJoin *) plan)->join.joinqual,
! 							"Join Filter", plan, indent, es);
! 			show_upper_qual(plan->qual, "Filter", plan, indent, es);
  			break;
  		case T_Agg:
  		case T_Group:
! 			show_upper_qual(plan->qual, "Filter", plan, indent, es);
  			break;
  		case T_Sort:
! 			show_sort_keys(plan, indent, es);
! 			show_sort_info((SortState *) planstate, indent, 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);
  			break;
  		default:
  			break;
  	}
  
  	/* initPlan-s */
  	if (plan->initPlan)
! 		ExplainSubNodes(planstate->initPlan, indent, es);
  
  	/* lefttree */
  	if (outerPlan(plan))
--- 762,813 ----
  
  				if (list_length(tidquals) > 1)
  					tidquals = list_make1(make_orclause(tidquals));
! 				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, es);
! 			show_upper_qual(plan->qual, "Filter", plan, es);
  			break;
  		case T_MergeJoin:
  			show_upper_qual(((MergeJoin *) plan)->mergeclauses,
! 							"Merge Cond", plan, es);
  			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
! 							"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, es);
  			show_upper_qual(((HashJoin *) plan)->join.joinqual,
! 							"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, es);
  			break;
  		case T_Sort:
! 			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, es);
! 			show_upper_qual(plan->qual, "Filter", plan, es);
  			break;
  		default:
  			break;
  	}
  
+ 	/* Increase indent for child plans. */
+ 	es->indent += 3;
+ 
  	/* initPlan-s */
  	if (plan->initPlan)
! 		ExplainSubNodes(planstate->initPlan, es);
  
  	/* lefttree */
  	if (outerPlan(plan))
***************
*** 811,841 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  		 */
  		ExplainNode(outerPlan(plan), outerPlanState(planstate),
  					IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
! 					indent + 3, es);
  	}
  
  	/* righttree */
  	if (innerPlan(plan))
  	{
  		ExplainNode(innerPlan(plan), innerPlanState(planstate),
! 					outerPlan(plan), indent + 3, es);
  	}
  
  	switch (nodeTag(plan)) {
  		case T_Append:
  			ExplainMemberNodes(((Append *) plan)->appendplans,
  							   ((AppendState *) planstate)->appendplans,
! 							   outer_plan, indent, es);
  			break;
  		case T_BitmapAnd:
  			ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
  							   ((BitmapAndState *) planstate)->bitmapplans,
! 							   outer_plan, indent, es);
  			break;
  		case T_BitmapOr:
  			ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
  							   ((BitmapOrState *) planstate)->bitmapplans,
! 							   outer_plan, indent, es);
  			break;
  		default:
  			break;
--- 819,849 ----
  		 */
  		ExplainNode(outerPlan(plan), outerPlanState(planstate),
  					IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
! 					NULL, es);
  	}
  
  	/* righttree */
  	if (innerPlan(plan))
  	{
  		ExplainNode(innerPlan(plan), innerPlanState(planstate),
! 					outerPlan(plan), NULL, es);
  	}
  
  	switch (nodeTag(plan)) {
  		case T_Append:
  			ExplainMemberNodes(((Append *) plan)->appendplans,
  							   ((AppendState *) planstate)->appendplans,
! 							   outer_plan, es);
  			break;
  		case T_BitmapAnd:
  			ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
  							   ((BitmapAndState *) planstate)->bitmapplans,
! 							   outer_plan, es);
  			break;
  		case T_BitmapOr:
  			ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
  							   ((BitmapOrState *) planstate)->bitmapplans,
! 							   outer_plan, es);
  			break;
  		default:
  			break;
***************
*** 847,865 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  		SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
  		Plan	   *subnode = subqueryscan->subplan;
  
! 		ExplainNode(subnode, subquerystate->subplan, NULL, indent + 3, es);
  	}
  
  	/* subPlan-s */
  	if (planstate->subPlan)
! 		ExplainSubNodes(planstate->subPlan, indent, es);
  }
  
  /*
   * Show the targetlist of a plan node
   */
  static void
! show_plan_tlist(Plan *plan, int indent, ExplainState *es)
  {
  	List	   *context;
  	bool		useprefix;
--- 855,876 ----
  		SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
  		Plan	   *subnode = subqueryscan->subplan;
  
! 		ExplainNode(subnode, subquerystate->subplan, NULL, NULL, es);
  	}
  
  	/* subPlan-s */
  	if (planstate->subPlan)
! 		ExplainSubNodes(planstate->subPlan, es);
! 
! 	/* restore previous indent level */
! 	es->indent = previous_indent;
  }
  
  /*
   * Show the targetlist of a plan node
   */
  static void
! show_plan_tlist(Plan *plan, ExplainState *es)
  {
  	List	   *context;
  	bool		useprefix;
***************
*** 884,890 **** show_plan_tlist(Plan *plan, int indent, ExplainState *es)
  	useprefix = list_length(es->rtable) > 1;
  
  	/* Emit line prefix */
! 	appendStringInfoSpaces(es->str, indent * 2);
  	appendStringInfo(es->str, "  Output: ");
  
  	/* Deparse each non-junk result column */
--- 895,901 ----
  	useprefix = list_length(es->rtable) > 1;
  
  	/* Emit line prefix */
! 	appendStringInfoSpaces(es->str, es->indent * 2);
  	appendStringInfo(es->str, "  Output: ");
  
  	/* Deparse each non-junk result column */
***************
*** 913,919 **** 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)
  {
  	List	   *context;
  	Node	   *node;
--- 924,930 ----
   */
  static void
  show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan,
! 		  bool useprefix, ExplainState *es)
  {
  	List	   *context;
  	Node	   *node;
***************
*** 936,942 **** show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan,
  	exprstr = deparse_expression(node, context, useprefix, false);
  
  	/* And add to str */
! 	appendStringInfoSpaces(es->str, indent * 2);
  	appendStringInfo(es->str, "  %s: %s\n", qlabel, exprstr);
  }
  
--- 947,953 ----
  	exprstr = deparse_expression(node, context, useprefix, false);
  
  	/* And add to str */
! 	appendStringInfoSpaces(es->str, es->indent * 2);
  	appendStringInfo(es->str, "  %s: %s\n", qlabel, exprstr);
  }
  
***************
*** 945,975 **** 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)
  {
  	bool		useprefix =
  		(outer_plan != NULL || IsA(scan_plan, SubqueryScan));
! 	show_qual(qual, qlabel, scan_plan, outer_plan, indent, 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)
  {
  	bool		useprefix = list_length(es->rtable) > 1;
  
! 	show_qual(qual, qlabel, plan, NULL, indent, useprefix, es);
  }
  
  /*
   * Show the sort keys for a Sort node.
   */
  static void
! show_sort_keys(Plan *sortplan, int indent, ExplainState *es)
  {
  	List	   *context;
  	bool		useprefix;
--- 956,984 ----
   */
  static void
  show_scan_qual(List *qual, const char *qlabel,
! 			   Plan *scan_plan, Plan *outer_plan, ExplainState *es)
  {
  	bool		useprefix =
  		(outer_plan != NULL || IsA(scan_plan, SubqueryScan));
! 	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, ExplainState *es)
  {
  	bool		useprefix = list_length(es->rtable) > 1;
  
! 	show_qual(qual, qlabel, plan, NULL, useprefix, es);
  }
  
  /*
   * Show the sort keys for a Sort node.
   */
  static void
! show_sort_keys(Plan *sortplan, ExplainState *es)
  {
  	List	   *context;
  	bool		useprefix;
***************
*** 981,987 **** 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 */
--- 990,996 ----
  	if (nkeys <= 0)
  		return;
  
! 	appendStringInfoSpaces(es->str, es->indent * 2);
  	appendStringInfoString(es->str, "  Sort Key: ");
  
  	/* Set up deparsing context */
***************
*** 1015,1032 **** show_sort_keys(Plan *sortplan, int indent, ExplainState *es)
   * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node
   */
  static void
! show_sort_info(SortState *sortstate, int indent, ExplainState *es)
  {
  	Assert(IsA(sortstate, SortState));
  	if (es->printAnalyze && sortstate->sort_Done &&
  		sortstate->tuplesortstate != NULL)
  	{
! 		char	   *sortinfo;
! 
! 		sortinfo = tuplesort_explain((Tuplesortstate *) sortstate->tuplesortstate);
! 		appendStringInfoSpaces(es->str, indent * 2);
! 		appendStringInfo(es->str, "  %s\n", sortinfo);
! 		pfree(sortinfo);
  	}
  }
  
--- 1024,1044 ----
   * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node
   */
  static void
! show_sort_info(SortState *sortstate, ExplainState *es)
  {
  	Assert(IsA(sortstate, SortState));
  	if (es->printAnalyze && sortstate->sort_Done &&
  		sortstate->tuplesortstate != NULL)
  	{
! 		Tuplesortstate	*state = (Tuplesortstate *) sortstate->tuplesortstate;
! 		char	   *method;
! 		char	   *type;
! 		long		spaceUsed;
! 
! 		method = tuplesort_explain(state, &type, &spaceUsed);
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		appendStringInfo(es->str, "  Sort Method:  %s  %s: %ldkB\n",
! 						 method, type, spaceUsed);
  	}
  }
  
***************
*** 1132,1146 **** ExplainScanTarget(Scan *plan, ExplainState *es)
   */
  static void
  ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
! 		           int indent, ExplainState *es)
  {
  	ListCell   *lst;
  	int			j = 0;
  
  	foreach(lst, plans)
  	{
! 		ExplainNode((Plan *) lfirst(lst), planstate[j], outer_plan,
! 					indent + 3, es);
  		++j;
  	}
  }
--- 1144,1157 ----
   */
  static void
  ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
! 		           ExplainState *es)
  {
  	ListCell   *lst;
  	int			j = 0;
  
  	foreach(lst, plans)
  	{
! 		ExplainNode((Plan *) lfirst(lst), planstate[j], outer_plan, NULL, es);
  		++j;
  	}
  }
***************
*** 1149,1155 **** ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
   * Explain a list of Subplans (or initPlans, which use SubPlan nodes).
   */
  static void
! ExplainSubNodes(List *plans, int indent, ExplainState *es)
  {
  	ListCell   *lst;
  
--- 1160,1166 ----
   * Explain a list of Subplans (or initPlans, which use SubPlan nodes).
   */
  static void
! ExplainSubNodes(List *plans, ExplainState *es)
  {
  	ListCell   *lst;
  
***************
*** 1158,1166 **** ExplainSubNodes(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);
  	}
  }
--- 1169,1175 ----
  		SubPlanState *sps = (SubPlanState *) lfirst(lst);
  		SubPlan    *sp = (SubPlan *) sps->xprstate.expr;
  
  		ExplainNode(exec_subplan_get_plan(es->pstmt, sp),
! 					sps->planstate, NULL, sp->plan_name, es);
  	}
  }
*** a/src/backend/commands/prepare.c
--- b/src/backend/commands/prepare.c
***************
*** 643,649 **** DropAllPreparedStatements(void)
  void
  ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
  					const char *queryString,
! 					ParamListInfo params, TupOutputState *tstate)
  {
  	PreparedStatement *entry;
  	const char *query_string;
--- 643,649 ----
  void
  ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
  					const char *queryString,
! 					ParamListInfo params, StringInfo str)
  {
  	PreparedStatement *entry;
  	const char *query_string;
***************
*** 708,726 **** ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
  			}
  
  			ExplainOnePlan(pstmt, stmt, query_string,
! 						   paramLI, tstate);
  		}
  		else
  		{
  			ExplainOneUtility((Node *) pstmt, stmt, query_string,
! 							  params, tstate);
  		}
  
  		/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
  
  		/* put a blank line between plans */
  		if (!is_last_query)
! 			do_text_output_oneline(tstate, "");
  	}
  
  	if (estate)
--- 708,726 ----
  			}
  
  			ExplainOnePlan(pstmt, stmt, query_string,
! 						   paramLI, str);
  		}
  		else
  		{
  			ExplainOneUtility((Node *) pstmt, stmt, query_string,
! 							  params, str);
  		}
  
  		/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
  
  		/* put a blank line between plans */
  		if (!is_last_query)
! 			appendStringInfoString(str, "\n");
  	}
  
  	if (estate)
*** a/src/backend/utils/adt/xml.c
--- b/src/backend/utils/adt/xml.c
***************
*** 1638,1645 **** map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
  	{
  		Oid			typeOut;
  		bool		isvarlena;
! 		char	   *p,
! 				   *str;
  
  		/*
  		 * Special XSD formatting for some data types
--- 1638,1644 ----
  	{
  		Oid			typeOut;
  		bool		isvarlena;
! 		char	   *str;
  
  		/*
  		 * Special XSD formatting for some data types
***************
*** 1789,1822 **** map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
  
  		/* 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;
  	}
  }
  
- 
  static char *
  _SPI_strdup(const char *s)
  {
--- 1788,1829 ----
  
  		/* otherwise, translate special characters as needed */
  		initStringInfo(&buf);
+ 		escape_xml(&buf, str);
+ 		return buf.data;
+ 	}
+ }
  
! /*
!  * Escape characters in text that have special meanings in XML.
!  */
! void
! escape_xml(StringInfo buf, const char *str)
! {
! 	const char *p;
! 
! 	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;
  		}
  	}
  }
  
  static char *
  _SPI_strdup(const char *s)
  {
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
***************
*** 2200,2218 **** tuplesort_restorepos(Tuplesortstate *state)
  }
  
  /*
!  * tuplesort_explain - produce a line of information for EXPLAIN ANALYZE
   *
   * 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.
   */
  char *
! tuplesort_explain(Tuplesortstate *state)
  {
- 	char	   *result = (char *) palloc(100);
- 	long		spaceUsed;
- 
  	/*
  	 * Note: it might seem we should print both memory and disk usage for a
  	 * disk-based sort.  However, the current code doesn't track memory space
--- 2200,2216 ----
  }
  
  /*
!  * tuplesort_explain - produce information for EXPLAIN ANALYZE
   *
   * This can be called after tuplesort_performsort() finishes to obtain
   * printable summary information about how the sort was performed.
   *
!  * The result is the sort method; the type (memory vs. disk) and amount
!  * of space used are returned using separate parameters.
   */
  char *
! tuplesort_explain(Tuplesortstate *state, char **type, long *spaceUsed)
  {
  	/*
  	 * Note: it might seem we should print both memory and disk usage for a
  	 * disk-based sort.  However, the current code doesn't track memory space
***************
*** 2223,2260 **** tuplesort_explain(Tuplesortstate *state)
  	 * tell us how much is actually used in sortcontext?
  	 */
  	if (state->tapeset)
! 		spaceUsed = LogicalTapeSetBlocks(state->tapeset) * (BLCKSZ / 1024);
  	else
! 		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);
  			else
! 				snprintf(result, 100,
! 						 "Sort Method:  quicksort  Memory: %ldkB",
! 						 spaceUsed);
  			break;
  		case TSS_SORTEDONTAPE:
! 			snprintf(result, 100,
! 					 "Sort Method:  external sort  Disk: %ldkB",
! 					 spaceUsed);
  			break;
  		case TSS_FINALMERGE:
! 			snprintf(result, 100,
! 					 "Sort Method:  external merge  Disk: %ldkB",
! 					 spaceUsed);
  			break;
  		default:
! 			snprintf(result, 100, "sort still in progress");
  			break;
  	}
- 
- 	return result;
  }
  
  
--- 2221,2252 ----
  	 * tell us how much is actually used in sortcontext?
  	 */
  	if (state->tapeset)
! 		*spaceUsed = LogicalTapeSetBlocks(state->tapeset) * (BLCKSZ / 1024);
  	else
! 		*spaceUsed = (state->allowedMem - state->availMem + 1023) / 1024;
  
  	switch (state->status)
  	{
  		case TSS_SORTEDINMEM:
+ 			*type = "Memory";
  			if (state->boundUsed)
! 				return "top-N heapsort";
  			else
! 				return "quicksort";
  			break;
  		case TSS_SORTEDONTAPE:
! 			*type = "Disk";
! 			return "external sort";
  			break;
  		case TSS_FINALMERGE:
! 			*type = "Disk";
! 			return "external merge";
  			break;
  		default:
! 			*type = "Storage";
! 			return "still in progress";
  			break;
  	}
  }
  
  
*** a/src/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 20,26 **** typedef void (*ExplainOneQuery_hook_type) (Query *query,
  													   ExplainStmt *stmt,
  													 const char *queryString,
  													   ParamListInfo params,
! 													 TupOutputState *tstate);
  extern PGDLLIMPORT ExplainOneQuery_hook_type ExplainOneQuery_hook;
  
  /* Hook for plugins to get control in explain_get_index_name() */
--- 20,26 ----
  													   ExplainStmt *stmt,
  													 const char *queryString,
  													   ParamListInfo params,
! 													 StringInfo str);
  extern PGDLLIMPORT ExplainOneQuery_hook_type ExplainOneQuery_hook;
  
  /* Hook for plugins to get control in explain_get_index_name() */
***************
*** 36,47 **** extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
  extern void ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
  				  const char *queryString,
  				  ParamListInfo params,
! 				  TupOutputState *tstate);
  
  extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  			   const char *queryString,
  			   ParamListInfo params,
! 			   TupOutputState *tstate);
  
  extern void ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
  				 bool analyze, bool verbose);
--- 36,47 ----
  extern void ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
  				  const char *queryString,
  				  ParamListInfo params,
! 				  StringInfo str);
  
  extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  			   const char *queryString,
  			   ParamListInfo params,
! 			   StringInfo str);
  
  extern void ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
  				 bool analyze, bool verbose);
*** a/src/include/commands/prepare.h
--- b/src/include/commands/prepare.h
***************
*** 42,48 **** extern void ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
  extern void DeallocateQuery(DeallocateStmt *stmt);
  extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
  					const char *queryString,
! 					ParamListInfo params, TupOutputState *tstate);
  
  /* Low-level access to stored prepared statements */
  extern void StorePreparedStatement(const char *stmt_name,
--- 42,48 ----
  extern void DeallocateQuery(DeallocateStmt *stmt);
  extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
  					const char *queryString,
! 					ParamListInfo params, StringInfo str);
  
  /* Low-level access to stored prepared statements */
  extern void StorePreparedStatement(const char *stmt_name,
*** a/src/include/utils/tuplesort.h
--- b/src/include/utils/tuplesort.h
***************
*** 84,90 **** extern bool tuplesort_getdatum(Tuplesortstate *state, bool forward,
  
  extern void tuplesort_end(Tuplesortstate *state);
  
! extern char *tuplesort_explain(Tuplesortstate *state);
  
  extern int	tuplesort_merge_order(long allowedMem);
  
--- 84,91 ----
  
  extern void tuplesort_end(Tuplesortstate *state);
  
! extern char *tuplesort_explain(Tuplesortstate *state, char **type,
! 					long *spaceUsed);
  
  extern int	tuplesort_merge_order(long allowedMem);
  
*** a/src/include/utils/xml.h
--- b/src/include/utils/xml.h
***************
*** 16,21 ****
--- 16,22 ----
  #define XML_H
  
  #include "fmgr.h"
+ #include "lib/stringinfo.h"
  #include "nodes/execnodes.h"
  #include "nodes/primnodes.h"
  
***************
*** 70,75 **** extern xmltype *xmlpi(char *target, text *arg, bool arg_is_null, bool *result_is
--- 71,77 ----
  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 void escape_xml(StringInfo buf, 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);
explain_format-v1.patchtext/x-diff; charset=US-ASCII; name=explain_format-v1.patchDownload
*** a/contrib/auto_explain/auto_explain.c
--- b/contrib/auto_explain/auto_explain.c
***************
*** 201,207 **** explain_ExecutorEnd(QueryDesc *queryDesc)
  			initStringInfo(&buf);
  			ExplainPrintPlan(&buf, queryDesc,
  						 queryDesc->doInstrument && auto_explain_log_analyze,
! 							 auto_explain_log_verbose);
  
  			/* Remove last line break */
  			if (buf.len > 0 && buf.data[buf.len - 1] == '\n')
--- 201,207 ----
  			initStringInfo(&buf);
  			ExplainPrintPlan(&buf, queryDesc,
  						 queryDesc->doInstrument && auto_explain_log_analyze,
! 							 auto_explain_log_verbose, EXPLAIN_FORMAT_TEXT);
  
  			/* Remove last line break */
  			if (buf.len > 0 && buf.data[buf.len - 1] == '\n')
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 31,36 ****
--- 31,37 ----
  #include "utils/lsyscache.h"
  #include "utils/tuplesort.h"
  #include "utils/snapmgr.h"
+ #include "utils/xml.h"
  
  
  /* Hook for plugins to get control in ExplainOneQuery() */
***************
*** 44,52 **** typedef struct ExplainState
--- 45,55 ----
  {
  	StringInfo	str;			/* output buffer */
  	int			indent;			/* indentation level */
+ 	int			needs_separator; /* true if already did a prop for this plan */
  	/* options */
  	bool		printTList;		/* print plan targetlists */
  	bool		printAnalyze;	/* print actual times */
+ 	ExplainFormat format;		/* desired output format */
  	/* other states */
  	PlannedStmt *pstmt;			/* top of plan */
  	List	   *rtable;			/* range table */
***************
*** 56,65 **** static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
  				const char *queryString,
  				ParamListInfo params, StringInfo str);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
! 				StringInfo buf);
  static double elapsed_time(instr_time *starttime);
  static void ExplainNode(Plan *plan, PlanState *planstate,
! 				Plan *outer_plan, char *qlabel, 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, bool useprefix, ExplainState *es);
--- 59,69 ----
  				const char *queryString,
  				ParamListInfo params, StringInfo str);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
! 				StringInfo buf, ExplainStmt *stmt, int *did_boilerplate);
  static double elapsed_time(instr_time *starttime);
  static void ExplainNode(Plan *plan, PlanState *planstate,
! 				Plan *outer_plan, char *relationship, char *qlabel,
! 				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, bool useprefix, ExplainState *es);
***************
*** 73,80 **** 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, ExplainState *es);
! static void ExplainSubNodes(List *plans, ExplainState *es);
  
  
  /*
   * ExplainQuery -
--- 77,91 ----
  static void ExplainScanTarget(Scan *plan, ExplainState *es);
  static void ExplainMemberNodes(List *plans, PlanState **planstate,
  		Plan *outer_plan, ExplainState *es);
! static void ExplainSubNodes(List *plans, char *relationship, ExplainState *es);
! 
! static void ExplainPropertyList(char *qlabel, List *data, ExplainState *es);
! static void ExplainPropertyText(const char *qlabel, const char *s,
! 	ExplainState *es);
! static void ExplainJSONLineEnding(ExplainState *es);
! static void ExplainXMLTag(const char *tagname, int closing, ExplainState *es);
  
+ static void escape_json(StringInfo buf, const char *str);
  
  /*
   * ExplainQuery -
***************
*** 110,119 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
  	/* initialize output buffer */
  	initStringInfo(&buf);
  
  	if (rewritten == NIL)
  	{
! 		/* In the case of an INSTEAD NOTHING, tell at least that */
! 		appendStringInfoString(&buf, "Query rewrites to nothing\n");
  	}
  	else
  	{
--- 121,139 ----
  	/* initialize output buffer */
  	initStringInfo(&buf);
  
+ 	if (stmt->format == EXPLAIN_FORMAT_XML)
+ 		appendStringInfoString(&buf, "<pgexplain>\n");
+ 	else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 		appendStringInfoString(&buf, "[");
+ 
  	if (rewritten == NIL)
  	{
! 		/*
! 		 * 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 (stmt->format == EXPLAIN_FORMAT_TEXT)
! 			appendStringInfoString(&buf, "Query rewrites to nothing\n");
  	}
  	else
  	{
***************
*** 124,133 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
  							queryString, params, &buf);
  			/* put a blank line between plans */
  			if (lnext(l) != NULL)
! 				appendStringInfoString(&buf, "\n");
  		}
  	}
  
  	/* output tuples */
  	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
  	do_text_output_multiline(tstate, buf.data);
--- 144,163 ----
  							queryString, params, &buf);
  			/* put a blank line between plans */
  			if (lnext(l) != NULL)
! 			{
! 				if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 					appendStringInfoChar(&buf, '\n');
! 				else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 					appendStringInfoChar(&buf, ',');
! 			}
  		}
  	}
  
+ 	if (stmt->format == EXPLAIN_FORMAT_XML)
+ 		appendStringInfoString(&buf, "</pgexplain>\n");
+ 	else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 		appendStringInfoString(&buf, "\n]\n");
+ 
  	/* output tuples */
  	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
  	do_text_output_multiline(tstate, buf.data);
***************
*** 203,212 **** ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
  		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
  							queryString, params, str);
  	else if (IsA(utilityStmt, NotifyStmt))
! 		appendStringInfoString(str, "NOTIFY\n");
! 	else
! 		appendStringInfoString(str,
  							   "Utility statements have no plan structure\n");
  }
  
  /*
--- 233,255 ----
  		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
  							queryString, params, str);
  	else if (IsA(utilityStmt, NotifyStmt))
! 	{
! 		if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 			appendStringInfoString(str, "NOTIFY\n");
! 		else if (stmt->format == EXPLAIN_FORMAT_XML)
! 			appendStringInfoString(str, "  <Notify />\n");
! 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 			appendStringInfoString(str, "\n  \"Notify\"");
! 	}
! 	else {
! 		if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 			appendStringInfoString(str,
  							   "Utility statements have no plan structure\n");
+ 		else if (stmt->format == EXPLAIN_FORMAT_XML)
+ 			appendStringInfoString(str, "  <Utility-Statement />\n");
+ 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 			appendStringInfoString(str, "\n  \"Utility-Statement\"");
+ 	}
  }
  
  /*
***************
*** 270,277 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  		totaltime += elapsed_time(&starttime);
  	}
  
  	/* Create textual dump of plan tree */
! 	ExplainPrintPlan(str, queryDesc, stmt->analyze, stmt->verbose);
  
  	/*
  	 * If we ran the command, run any AFTER triggers it queued.  (Note this
--- 313,327 ----
  		totaltime += elapsed_time(&starttime);
  	}
  
+ 	/* Opening boilerplate */
+ 	if (stmt->format == EXPLAIN_FORMAT_XML)
+ 		appendStringInfoString(str, "  <Query>\n");
+ 	else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 		appendStringInfoString(str, "\n  {");
+ 
  	/* Create textual dump of plan tree */
! 	ExplainPrintPlan(str, queryDesc, stmt->analyze, stmt->verbose,
! 		stmt->format);
  
  	/*
  	 * If we ran the command, run any AFTER triggers it queued.  (Note this
***************
*** 293,309 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  		int			numrels = queryDesc->estate->es_num_result_relations;
  		List	   *targrels = queryDesc->estate->es_trig_target_relations;
  		int			nr;
  		ListCell   *l;
  
  		show_relname = (numrels > 1 || targrels != NIL);
  		rInfo = queryDesc->estate->es_result_relations;
  		for (nr = 0; nr < numrels; rInfo++, nr++)
! 			report_triggers(rInfo, show_relname, str);
  
  		foreach(l, targrels)
  		{
  			rInfo = (ResultRelInfo *) lfirst(l);
! 			report_triggers(rInfo, show_relname, str);
  		}
  	}
  
--- 343,369 ----
  		int			numrels = queryDesc->estate->es_num_result_relations;
  		List	   *targrels = queryDesc->estate->es_trig_target_relations;
  		int			nr;
+ 		int			did_boilerplate;
  		ListCell   *l;
  
  		show_relname = (numrels > 1 || targrels != NIL);
  		rInfo = queryDesc->estate->es_result_relations;
  		for (nr = 0; nr < numrels; rInfo++, nr++)
! 			report_triggers(rInfo, show_relname, str, stmt, &did_boilerplate);
  
  		foreach(l, targrels)
  		{
  			rInfo = (ResultRelInfo *) lfirst(l);
! 			report_triggers(rInfo, show_relname, str, stmt, &did_boilerplate);
! 		}
! 
! 		/* Closing boilerplate */
! 		if (did_boilerplate)
! 		{
! 			if (stmt->format == EXPLAIN_FORMAT_XML)
! 				appendStringInfoString(str, "    </Triggers>\n");
! 			else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 				appendStringInfoString(str, "\n    }");
  		}
  	}
  
***************
*** 326,333 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  	totaltime += elapsed_time(&starttime);
  
  	if (stmt->analyze)
! 		appendStringInfo(str, "Total runtime: %.3f ms\n",
! 						 1000.0 * totaltime);
  }
  
  /*
--- 386,408 ----
  	totaltime += elapsed_time(&starttime);
  
  	if (stmt->analyze)
! 	{
! 		if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 			appendStringInfo(str, "Total runtime: %.3f ms\n",
! 							 1000.0 * totaltime);
! 		else if (stmt->format == EXPLAIN_FORMAT_XML)
! 			appendStringInfo(str, "    <Total-Runtime>%.3f</Total-Runtime>\n",
! 							 1000.0 * totaltime);
! 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 			appendStringInfo(str, ",\n    \"Total runtime\" : \"%.3f\"",
! 							 1000.0 * totaltime);
! 	}
! 
! 	/* Closing boilerplate */
! 	if (stmt->format == EXPLAIN_FORMAT_XML)
! 		appendStringInfoString(str, "  </Query>\n");
! 	else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 		appendStringInfoString(str, "\n  }");
  }
  
  /*
***************
*** 341,361 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
   */
  void
  ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
! 				 bool analyze, bool verbose)
  {
  	ExplainState es;
  
  	Assert(queryDesc->plannedstmt != NULL);
  
  	memset(&es, 0, sizeof(es));
  	es.str = str;
  	es.printTList = verbose;
  	es.printAnalyze = analyze;
  	es.pstmt = queryDesc->plannedstmt;
  	es.rtable = queryDesc->plannedstmt->rtable;
  
  	ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate,
! 				NULL, NULL, &es);
  }
  
  /*
--- 416,457 ----
   */
  void
  ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
! 				 bool analyze, bool verbose, ExplainFormat format)
  {
  	ExplainState es;
  
  	Assert(queryDesc->plannedstmt != NULL);
  
+ 	/* Handle NULL plan. */
+ 	if (!queryDesc->plannedstmt->planTree)
+ 	{
+ 		switch (format)
+ 		{
+ 			case EXPLAIN_FORMAT_TEXT:
+ 				appendStringInfoChar(str, '\n');
+ 				break;
+ 			case EXPLAIN_FORMAT_XML:
+ 				appendStringInfoString(str, "  <Plan/>\n");
+ 				break;
+ 			case EXPLAIN_FORMAT_JSON:
+ 				appendStringInfoString(str, "  {}\n");
+ 				break;
+ 		}
+ 		return;
+ 	}
+ 
  	memset(&es, 0, sizeof(es));
  	es.str = str;
  	es.printTList = verbose;
  	es.printAnalyze = analyze;
+ 	es.format = format;
  	es.pstmt = queryDesc->plannedstmt;
  	es.rtable = queryDesc->plannedstmt->rtable;
+ 	if (format != EXPLAIN_FORMAT_TEXT)
+ 		es.indent = 1;
  
  	ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate,
! 				NULL, NULL, NULL, &es);
  }
  
  /*
***************
*** 363,374 **** ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
   *		report execution stats for a single relation's triggers
   */
  static void
! report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf)
  {
  	int			nt;
  
  	if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
  		return;
  	for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
  	{
  		Trigger    *trig = rInfo->ri_TrigDesc->triggers + nt;
--- 459,472 ----
   *		report execution stats for a single relation's triggers
   */
  static void
! report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf,
! 				ExplainStmt *stmt, int *did_boilerplate)
  {
  	int			nt;
  
  	if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
  		return;
+ 
  	for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
  	{
  		Trigger    *trig = rInfo->ri_TrigDesc->triggers + nt;
***************
*** 385,406 **** 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)
  		{
! 			appendStringInfo(buf, "Trigger for constraint %s", conname);
  			pfree(conname);
  		}
  		else
! 			appendStringInfo(buf, "Trigger %s", trig->tgname);
  
! 		if (show_relname)
! 			appendStringInfo(buf, " on %s",
  							 RelationGetRelationName(rInfo->ri_RelationDesc));
  
! 		appendStringInfo(buf, ": time=%.3f calls=%.0f\n",
! 						 1000.0 * instr->total, instr->ntuples);
  	}
  }
  
  /* Compute elapsed time in seconds since given timestamp */
--- 483,577 ----
  		if (instr->ntuples == 0)
  			continue;
  
+ 		/* Opening boilerplate for this trigger */
+ 		if (stmt->format == EXPLAIN_FORMAT_XML)
+ 		{
+ 			if (!*did_boilerplate)
+ 				appendStringInfoString(buf, "    <Triggers>\n");
+ 			appendStringInfoString(buf, "      <Trigger>\n");
+ 		}
+ 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 		{
+ 			if (*did_boilerplate)
+ 				appendStringInfoChar(buf, ',');
+ 			else
+ 				appendStringInfoString(buf, ",\n    \"Triggers\": {");
+ 			appendStringInfoString(buf, "\n      {");
+ 		}
+ 		*did_boilerplate = 1;
+ 
  		if (OidIsValid(trig->tgconstraint) &&
  			(conname = get_constraint_name(trig->tgconstraint)) != NULL)
  		{
! 			if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 				appendStringInfo(buf, "Trigger for constraint %s", conname);
! 			else if (stmt->format == EXPLAIN_FORMAT_XML)
! 			{
! 				appendStringInfoString(buf, "        <Constraint>");
! 				escape_xml(buf, conname);
! 				appendStringInfoString(buf, "</Constraint>\n");
! 			}
! 			else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 			{
! 				appendStringInfo(buf, "\n        \"Constraint\": ");
! 				escape_json(buf, conname);
! 			}
  			pfree(conname);
  		}
  		else
! 		{
! 			if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 				appendStringInfo(buf, "Trigger %s", trig->tgname);
! 			else if (stmt->format == EXPLAIN_FORMAT_XML)
! 			{
! 				appendStringInfoString(buf, "        <Trigger>");
! 				escape_xml(buf, trig->tgname);
! 				appendStringInfoString(buf, "</Trigger>\n");
! 			}
! 			else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 			{
! 				appendStringInfo(buf, "\n        \"Trigger\": ");
! 				escape_json(buf, trig->tgname);
! 			}
! 		}
  
! 		if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 		{
! 			if (show_relname)
! 				appendStringInfo(buf, " on %s",
  							 RelationGetRelationName(rInfo->ri_RelationDesc));
+ 			appendStringInfo(buf, ": time=%.3f calls=%.0f\n",
+ 							 1000.0 * instr->total, instr->ntuples);
+ 		}
+ 		else if (stmt->format == EXPLAIN_FORMAT_XML)
+ 		{
+ 			/* In non-text formats, we always show the relation name. */
+ 			appendStringInfoString(buf, "        <Relation>");
+ 			escape_xml(buf, RelationGetRelationName(rInfo->ri_RelationDesc));
+ 			appendStringInfoString(buf, "</Relation>\n");
+ 			appendStringInfo(buf, "        <Time>%.3f</Time>\n",
+ 							 1000.0 * instr->total);
+ 			appendStringInfo(buf,"        <Calls>%.0f</Calls>\n",
+ 							 instr->ntuples);
+ 		}
+ 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
+ 		{
+ 			/* In non-text formats, we always show the relation name. */
+ 			appendStringInfoString(buf, ",\n        \"Relation\": ");
+ 			escape_json(buf, RelationGetRelationName(rInfo->ri_RelationDesc));
+ 			appendStringInfo(buf, ",\n        \"Time\": \"%.3f\"",
+ 							 1000.0 * instr->total);
+ 			appendStringInfo(buf, ",\n        \"Calls\": \"%.0f\"",
+ 							 instr->ntuples);
+ 		}
  
! 		/* Closing boilerplate for this trigger */
! 		if (stmt->format == EXPLAIN_FORMAT_XML)
! 			appendStringInfoString(buf, "      </Trigger>\n");
! 		else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 			appendStringInfoString(buf, "\n      }");
  	}
+ 
  }
  
  /* Compute elapsed time in seconds since given timestamp */
***************
*** 428,683 **** elapsed_time(instr_time *starttime)
   */
  static void
  ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
! 			char *qlabel, ExplainState *es)
  {
  	const char *pname;
! 	int previous_indent = es->indent;
  
! 	if (qlabel)
! 	{
! 		Assert(es->indent >= 3);
! 		++es->indent;
! 		appendStringInfoSpaces(es->str, es->indent * 2 - 6);
! 		appendStringInfo(es->str, "%s\n", qlabel);
! 	}
  
! 	if (es->indent)
  	{
! 		Assert(es->indent >= 2);
! 		appendStringInfoSpaces(es->str, 2 * es->indent - 4);
! 		appendStringInfoString(es->str, "->  ");
! 	}
  
! 	if (plan == NULL)
! 	{
! 		appendStringInfoChar(es->str, '\n');
! 		return;
  	}
  
  	switch (nodeTag(plan))
  	{
  		case T_Result:
! 			pname = "Result";
  			break;
  		case T_Append:
! 			pname = "Append";
  			break;
  		case T_RecursiveUnion:
! 			pname = "Recursive Union";
  			break;
  		case T_BitmapAnd:
! 			pname = "BitmapAnd";
  			break;
  		case T_BitmapOr:
! 			pname = "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;
! 			}
  			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;
! 			}
  			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;
! 			}
  			break;
  		case T_SeqScan:
! 			pname = "Seq Scan";
  			break;
  		case T_IndexScan:
! 			pname = "Index Scan";
  			break;
  		case T_BitmapIndexScan:
! 			pname = "Bitmap Index Scan";
  			break;
  		case T_BitmapHeapScan:
! 			pname = "Bitmap Heap Scan";
  			break;
  		case T_TidScan:
! 			pname = "Tid Scan";
  			break;
  		case T_SubqueryScan:
! 			pname = "Subquery Scan";
  			break;
  		case T_FunctionScan:
! 			pname = "Function Scan";
  			break;
  		case T_ValuesScan:
! 			pname = "Values Scan";
  			break;
  		case T_CteScan:
! 			pname = "CTE Scan";
  			break;
  		case T_WorkTableScan:
! 			pname = "WorkTable Scan";
  			break;
  		case T_Material:
! 			pname = "Materialize";
  			break;
  		case T_Sort:
! 			pname = "Sort";
  			break;
  		case T_Group:
! 			pname = "Group";
  			break;
  		case T_Agg:
  			switch (((Agg *) plan)->aggstrategy)
  			{
  				case AGG_PLAIN:
  					pname = "Aggregate";
  					break;
  				case AGG_SORTED:
  					pname = "GroupAggregate";
  					break;
  				case AGG_HASHED:
  					pname = "HashAggregate";
  					break;
  				default:
  					pname = "Aggregate ???";
  					break;
  			}
  			break;
  		case T_WindowAgg:
! 			pname = "WindowAgg";
  			break;
  		case T_Unique:
! 			pname = "Unique";
  			break;
  		case T_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;
! 					}
  					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;
! 					}
  					break;
  				default:
  					pname = "SetOp ???";
  					break;
  			}
  			break;
  		case T_Limit:
! 			pname = "Limit";
  			break;
  		case T_Hash:
! 			pname = "Hash";
  			break;
  		default:
! 			pname = "???";
  			break;
  	}
  
! 	appendStringInfoString(es->str, pname);
  	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));
  			/* FALL THRU */
  		case T_SeqScan:
  		case T_BitmapHeapScan:
--- 599,825 ----
   */
  static void
  ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
! 			char *relationship, char *qlabel, ExplainState *es)
  {
+ 	bool		moreplans;
  	const char *pname;
! 	const char *sname;
! 	const char *strategy = NULL;
! 	int			previous_indent = es->indent;
! 	int			previous_needs_separator = 0;
! 	List	   *memberplans = NIL;
! 	PlanState **memberplanstates = NULL;
  
! 	Assert(plan);
  
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
  	{
! 		if (qlabel)
! 		{
! 			Assert(es->indent >= 3);
! 			++es->indent;
! 			appendStringInfoSpaces(es->str, es->indent * 2 - 6);
! 			appendStringInfo(es->str, "%s\n", qlabel);
! 		}
  
! 		if (es->indent)
! 		{
! 			Assert(es->indent >= 2);
! 			appendStringInfoSpaces(es->str, 2 * es->indent - 4);
! 			appendStringInfoString(es->str, "->  ");
! 		}
  	}
  
  	switch (nodeTag(plan))
  	{
  		case T_Result:
! 			pname = sname = "Result";
  			break;
  		case T_Append:
! 			pname = sname = "Append";
  			break;
  		case T_RecursiveUnion:
! 			pname = sname = "Recursive Union";
  			break;
  		case T_BitmapAnd:
! 			pname = sname = "BitmapAnd";
  			break;
  		case T_BitmapOr:
! 			pname = sname = "BitmapOr";
  			break;
  		case T_NestLoop:
! 			sname = pname = "Nested Loop";
  			break;
  		case T_MergeJoin:
! 			sname = "Merge Join";
! 			pname = "Merge";		/* "Join" gets added by jointype switch */
  			break;
  		case T_HashJoin:
! 			sname = "Hash Join";
! 			pname = "Hash";			/* "Join" gets added by jointype switch */
  			break;
  		case T_SeqScan:
! 			pname = sname = "Seq Scan";
  			break;
  		case T_IndexScan:
! 			pname = sname = "Index Scan";
  			break;
  		case T_BitmapIndexScan:
! 			pname = sname = "Bitmap Index Scan";
  			break;
  		case T_BitmapHeapScan:
! 			pname = sname = "Bitmap Heap Scan";
  			break;
  		case T_TidScan:
! 			pname = sname = "Tid Scan";
  			break;
  		case T_SubqueryScan:
! 			pname = sname = "Subquery Scan";
  			break;
  		case T_FunctionScan:
! 			pname = sname = "Function Scan";
  			break;
  		case T_ValuesScan:
! 			pname = sname = "Values Scan";
  			break;
  		case T_CteScan:
! 			pname = sname = "CTE Scan";
  			break;
  		case T_WorkTableScan:
! 			pname = sname = "WorkTable Scan";
  			break;
  		case T_Material:
! 			pname = sname = "Materialize";
  			break;
  		case T_Sort:
! 			pname = sname = "Sort";
  			break;
  		case T_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 = sname = "WindowAgg";
  			break;
  		case T_Unique:
! 			pname = sname = "Unique";
  			break;
  		case T_SetOp:
+ 			sname = "SetOp";
  			switch (((SetOp *) plan)->strategy)
  			{
  				case SETOP_SORTED:
! 					pname = "SetOp";
! 					strategy = "Sorted";
  					break;
  				case SETOP_HASHED:
! 					pname = "HashSetOp";
! 					strategy = "Hashed";
  					break;
  				default:
  					pname = "SetOp ???";
+ 					strategy = "???";
  					break;
  			}
  			break;
  		case T_Limit:
! 			pname = sname = "Limit";
  			break;
  		case T_Hash:
! 			pname = sname = "Hash";
  			break;
  		default:
! 			pname = sname = "???";
  			break;
  	}
  
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 		appendStringInfoString(es->str, pname);
! 	else
! 	{
! 		++es->indent;
! 		if (es->format == EXPLAIN_FORMAT_XML)
! 		{
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			ExplainXMLTag("Plan", 0, es);
! 			appendStringInfoChar(es->str, '\n');
! 		}
! 		else if (es->format == EXPLAIN_FORMAT_JSON)
! 		{
! 			ExplainJSONLineEnding(es);
! 			previous_needs_separator = es->needs_separator;
! 			es->needs_separator = 0;
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			if (!relationship)
! 				appendStringInfoString(es->str, "\"Plan\": ");
! 			appendStringInfoChar(es->str, '{');
! 		}
! 		++es->indent;
! 		ExplainPropertyText("Node Type", sname, es);
! 		if (relationship)
! 			ExplainPropertyText("Parent Relationship", relationship, es);
! 		if (qlabel)
! 			ExplainPropertyText("Parent Label", qlabel, es);
! 		if (strategy)
! 			ExplainPropertyText("Strategy", strategy, es);
! 	}
! 
  	switch (nodeTag(plan))
  	{
  		case T_IndexScan:
! 			{
! 				IndexScan *indexscan = (IndexScan *) plan;
! 				const char *index =
! 					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", index);
! 				}
! 				else
! 				{
! 					char *scandir;
! 					switch (((IndexScan *) plan)->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", index, es);
! 				}
! 			}
  			/* FALL THRU */
  		case T_SeqScan:
  		case T_BitmapHeapScan:
***************
*** 690,705 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  			ExplainScanTarget((Scan *) plan, es);
  			break;
  		case T_BitmapIndexScan:
! 			appendStringInfo(es->str, " on %s",
! 				explain_get_index_name(((BitmapIndexScan *) plan)->indexid));
  			break;
  		default:
  			break;
  	}
  
! 	appendStringInfo(es->str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
! 					 plan->startup_cost, plan->total_cost,
! 					 plan->plan_rows, plan->plan_width);
  
  	/*
  	 * We have to forcibly clean up the instrumentation state because we
--- 832,939 ----
  			ExplainScanTarget((Scan *) plan, es);
  			break;
  		case T_BitmapIndexScan:
! 			{
! 				BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
! 				const char *index =
! 					explain_get_index_name(bitmapindexscan->indexid);
! 				if (es->format == EXPLAIN_FORMAT_TEXT)
! 					appendStringInfo(es->str, " on %s", index);
! 				else
! 					ExplainPropertyText("Index Name", index, es);
! 				break;
! 			}
! 			break;
! 		case T_MergeJoin:
! 		case T_HashJoin:
! 		case T_NestLoop:
! 			{
! 				char *jointype = NULL;
! 				switch (((Join *) plan)->jointype)
! 				{
! 					case JOIN_INNER:
! 						if (es->format != EXPLAIN_FORMAT_TEXT)
! 							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)
! 				{
! 					if (jointype)
! 						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:
+ 			{
+ 				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->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
! 	{
! 		char b[256];
! 		sprintf(b, "%.2f", plan->startup_cost);
! 		ExplainPropertyText("Startup Cost", b, es);
! 		sprintf(b, "%.2f", plan->total_cost);
! 		ExplainPropertyText("Total Cost", b, es);
! 		sprintf(b, "%.0f", plan->plan_rows);
! 		ExplainPropertyText("Plan Rows", b, es);
! 		sprintf(b, "%d", plan->plan_width);
! 		ExplainPropertyText("Plan Width", b, es);
! 	}
  
  	/*
  	 * We have to forcibly clean up the instrumentation state because we
***************
*** 711,727 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  	if (planstate->instrument && planstate->instrument->nloops > 0)
  	{
  		double		nloops = planstate->instrument->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);
  	}
  	else if (es->printAnalyze)
! 		appendStringInfo(es->str, " (never executed)");
! 	appendStringInfoChar(es->str, '\n');
  
  	/* target list */
  	if (es->printTList)
--- 945,985 ----
  	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;
  
! 		if (es->format == EXPLAIN_FORMAT_TEXT)
! 		{
! 			appendStringInfo(es->str,
! 							 " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
! 							startup_sec, total_sec, rows, nloops);
! 		}
! 		else
! 		{
! 			char b[256];
! 			sprintf(b, "%.3f", startup_sec);
! 			ExplainPropertyText("Actual Startup Time", b, es);
! 			sprintf(b, "%.3f", total_sec);
! 			ExplainPropertyText("Actual Total Time", b, es);
! 			sprintf(b, "%.0f", rows);
! 			ExplainPropertyText("Actual Rows", b, es);
! 			sprintf(b, "%.0f", nloops);
! 			ExplainPropertyText("Actual Loops", b, es);
! 		}
  	}
  	else if (es->printAnalyze)
! 	{
! 		if (es->format == EXPLAIN_FORMAT_TEXT)
! 			appendStringInfo(es->str, " (never executed)");
! 		else
! 			ExplainPropertyText("Actual Loops", "0", es);
! 	}
! 
! 	/* in text format, first line ends here */
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 		appendStringInfoChar(es->str, '\n');
  
  	/* target list */
  	if (es->printTList)
***************
*** 798,813 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  							"One-Time Filter", plan, es);
  			show_upper_qual(plan->qual, "Filter", plan, es);
  			break;
  		default:
  			break;
  	}
  
! 	/* Increase indent for child plans. */
! 	es->indent += 3;
  
  	/* initPlan-s */
  	if (plan->initPlan)
! 		ExplainSubNodes(planstate->initPlan, es);
  
  	/* lefttree */
  	if (outerPlan(plan))
--- 1056,1104 ----
  							"One-Time Filter", plan, es);
  			show_upper_qual(plan->qual, "Filter", plan, es);
  			break;
+ 		case T_Append:
+ 			memberplans = ((Append *) plan)->appendplans;
+ 			memberplanstates = ((AppendState *) planstate)->appendplans;
+ 			break;
+ 		case T_BitmapAnd:
+ 			memberplans = ((BitmapAnd *) plan)->bitmapplans;
+ 			memberplanstates = ((BitmapAndState *) planstate)->bitmapplans;
+ 			break;
+ 		case T_BitmapOr:
+ 			memberplans = ((BitmapOr *) plan)->bitmapplans;
+ 			memberplanstates = ((BitmapOrState *) planstate)->bitmapplans;
+ 			break;
  		default:
  			break;
  	}
  
! 	/* Get ready to display the child plans. */
! 	moreplans = plan->initPlan || outerPlan(plan) || innerPlan(plan)
! 				|| IsA(plan, SubqueryScan) || memberplans
! 				|| planstate->subPlan;
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 		es->indent += 3;
! 	else if (moreplans)
! 	{
! 		if (es->format == EXPLAIN_FORMAT_XML)
! 		{
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			ExplainXMLTag("Plans", 0, es);
! 			appendStringInfoChar(es->str, '\n');
! 		}
! 		else if (es->format == EXPLAIN_FORMAT_JSON)
! 		{
! 			ExplainJSONLineEnding(es);
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			escape_json(es->str, "Plans");
! 			appendStringInfoString(es->str, ": [");
! 			es->needs_separator = 0;
! 		}
! 	}
  
  	/* initPlan-s */
  	if (plan->initPlan)
! 		ExplainSubNodes(planstate->initPlan, "InitPlan", es);
  
  	/* lefttree */
  	if (outerPlan(plan))
***************
*** 819,853 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  		 */
  		ExplainNode(outerPlan(plan), outerPlanState(planstate),
  					IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
! 					NULL, es);
  	}
  
  	/* righttree */
  	if (innerPlan(plan))
  	{
  		ExplainNode(innerPlan(plan), innerPlanState(planstate),
! 					outerPlan(plan), NULL, es);
  	}
  
! 	switch (nodeTag(plan)) {
! 		case T_Append:
! 			ExplainMemberNodes(((Append *) plan)->appendplans,
! 							   ((AppendState *) planstate)->appendplans,
! 							   outer_plan, es);
! 			break;
! 		case T_BitmapAnd:
! 			ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
! 							   ((BitmapAndState *) planstate)->bitmapplans,
! 							   outer_plan, es);
! 			break;
! 		case T_BitmapOr:
! 			ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
! 							   ((BitmapOrState *) planstate)->bitmapplans,
! 							   outer_plan, es);
! 			break;
! 		default:
! 			break;
! 	}
  
  	if (IsA(plan, SubqueryScan))
  	{
--- 1110,1127 ----
  		 */
  		ExplainNode(outerPlan(plan), outerPlanState(planstate),
  					IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
! 					"Outer", NULL, es);
  	}
  
  	/* righttree */
  	if (innerPlan(plan))
  	{
  		ExplainNode(innerPlan(plan), innerPlanState(planstate),
! 					outerPlan(plan), "Inner", NULL, es);
  	}
  
! 	if (memberplans)
! 		ExplainMemberNodes(memberplans, memberplanstates, outer_plan, es);
  
  	if (IsA(plan, SubqueryScan))
  	{
***************
*** 855,869 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  		SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
  		Plan	   *subnode = subqueryscan->subplan;
  
! 		ExplainNode(subnode, subquerystate->subplan, NULL, NULL, es);
  	}
  
  	/* subPlan-s */
  	if (planstate->subPlan)
! 		ExplainSubNodes(planstate->subPlan, es);
  
! 	/* restore previous indent level */
  	es->indent = previous_indent;
  }
  
  /*
--- 1129,1181 ----
  		SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
  		Plan	   *subnode = subqueryscan->subplan;
  
! 		ExplainNode(subnode, subquerystate->subplan, NULL,
! 					"Subquery", NULL, es);
  	}
  
  	/* subPlan-s */
  	if (planstate->subPlan)
! 		ExplainSubNodes(planstate->subPlan, "SubPlan", es);
! 
! 	/* close out output for this plan node */
! 	if (es->format != EXPLAIN_FORMAT_TEXT)
! 	{
! 		/* end of init, outer, inner, subquery, and subplans */
! 		if (moreplans)
! 		{
! 			if (es->format == EXPLAIN_FORMAT_XML)
! 			{
! 				appendStringInfoSpaces(es->str, 2 * es->indent);
! 				ExplainXMLTag("Plans", 1, es);
! 				appendStringInfoChar(es->str, '\n');
! 			}
! 			else if (es->format == EXPLAIN_FORMAT_JSON)
! 			{
! 				appendStringInfoChar(es->str, '\n');
! 				appendStringInfoSpaces(es->str, 2 * es->indent);
! 				appendStringInfoString(es->str, "]");
! 			}
! 		}
! 
! 		/* end of plan */
! 		--es->indent;
! 		if (es->format == EXPLAIN_FORMAT_XML)
! 		{
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			ExplainXMLTag("Plan", 1, es);
! 			appendStringInfoChar(es->str, '\n');
! 		}
! 		else if (es->format == EXPLAIN_FORMAT_JSON)
! 		{
! 			appendStringInfoChar(es->str, '\n');
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			appendStringInfoChar(es->str, '}');
! 		}
! 	}
  
! 	/* restore previous indent and separator state */
  	es->indent = previous_indent;
+ 	es->needs_separator = previous_needs_separator;
  }
  
  /*
***************
*** 873,878 **** static void
--- 1185,1191 ----
  show_plan_tlist(Plan *plan, ExplainState *es)
  {
  	List	   *context;
+ 	List	   *result = NIL;
  	bool		useprefix;
  	ListCell   *lc;
  	int			i;
***************
*** 894,919 **** show_plan_tlist(Plan *plan, ExplainState *es)
  									   es->pstmt->subplans);
  	useprefix = list_length(es->rtable) > 1;
  
- 	/* Emit line prefix */
- 	appendStringInfoSpaces(es->str, es->indent * 2);
- 	appendStringInfo(es->str, "  Output: ");
- 
  	/* Deparse each non-junk result column */
  	i = 0;
  	foreach(lc, plan->targetlist)
  	{
  		TargetEntry *tle = (TargetEntry *) lfirst(lc);
! 
! 		if (tle->resjunk)
! 			continue;
! 		if (i++ > 0)
! 			appendStringInfo(es->str, ", ");
! 		appendStringInfoString(es->str,
! 							   deparse_expression((Node *) tle->expr, context,
  												  useprefix, false));
  	}
  
! 	appendStringInfoChar(es->str, '\n');
  }
  
  /*
--- 1207,1225 ----
  									   es->pstmt->subplans);
  	useprefix = list_length(es->rtable) > 1;
  
  	/* Deparse each non-junk result column */
  	i = 0;
  	foreach(lc, plan->targetlist)
  	{
  		TargetEntry *tle = (TargetEntry *) lfirst(lc);
! 		if (!tle->resjunk)
! 			result = lappend(result,
! 						     deparse_expression((Node *) tle->expr, context,
  												  useprefix, false));
  	}
  
! 	/* Print results. */
! 	ExplainPropertyList("Output", result, es);
  }
  
  /*
***************
*** 947,954 **** show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan,
  	exprstr = deparse_expression(node, context, useprefix, false);
  
  	/* And add to str */
! 	appendStringInfoSpaces(es->str, es->indent * 2);
! 	appendStringInfo(es->str, "  %s: %s\n", qlabel, exprstr);
  }
  
  /*
--- 1253,1259 ----
  	exprstr = deparse_expression(node, context, useprefix, false);
  
  	/* And add to str */
! 	ExplainPropertyText(qlabel, exprstr, es);
  }
  
  /*
***************
*** 981,986 **** static void
--- 1286,1292 ----
  show_sort_keys(Plan *sortplan, ExplainState *es)
  {
  	List	   *context;
+ 	List	   *result = NIL;
  	bool		useprefix;
  	int			keyno;
  	char	   *exprstr;
***************
*** 990,998 **** show_sort_keys(Plan *sortplan, ExplainState *es)
  	if (nkeys <= 0)
  		return;
  
- 	appendStringInfoSpaces(es->str, es->indent * 2);
- 	appendStringInfoString(es->str, "  Sort Key: ");
- 
  	/* Set up deparsing context */
  	context = deparse_context_for_plan((Node *) sortplan,
  									   NULL,
--- 1296,1301 ----
***************
*** 1011,1023 **** show_sort_keys(Plan *sortplan, ExplainState *es)
  		/* Deparse the expression, showing any top-level cast */
  		exprstr = deparse_expression((Node *) target->expr, context,
  									 useprefix, true);
! 		/* And add to str */
! 		if (keyno > 0)
! 			appendStringInfo(es->str, ", ");
! 		appendStringInfoString(es->str, exprstr);
  	}
  
! 	appendStringInfo(es->str, "\n");
  }
  
  /*
--- 1314,1323 ----
  		/* Deparse the expression, showing any top-level cast */
  		exprstr = deparse_expression((Node *) target->expr, context,
  									 useprefix, true);
! 		result = lappend(result, exprstr);
  	}
  
! 	ExplainPropertyList("Sort Keys", result, es);
  }
  
  /*
***************
*** 1036,1044 **** show_sort_info(SortState *sortstate, ExplainState *es)
  		long		spaceUsed;
  
  		method = tuplesort_explain(state, &type, &spaceUsed);
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		appendStringInfo(es->str, "  Sort Method:  %s  %s: %ldkB\n",
! 						 method, type, spaceUsed);
  	}
  }
  
--- 1336,1354 ----
  		long		spaceUsed;
  
  		method = tuplesort_explain(state, &type, &spaceUsed);
! 
! 		if (es->format == EXPLAIN_FORMAT_TEXT)
! 		{
! 			appendStringInfoSpaces(es->str, es->indent * 2);
! 			appendStringInfo(es->str, "  Sort Method:  %s  %s: %ldkB\n",
! 							 method, type, spaceUsed);
! 		}
! 		else {
! 			char spaceUsedStr[128];
! 			sprintf(spaceUsedStr, "%ldkB", spaceUsed);
! 			ExplainPropertyText("Sort Method", method, es);
! 			ExplainPropertyText("Sort Space Used", spaceUsedStr, es);
! 		}
  	}
  }
  
***************
*** 1075,1080 **** static void
--- 1385,1391 ----
  ExplainScanTarget(Scan *plan, ExplainState *es)
  {
  	char *objectname = NULL;
+ 	char *objecttag = NULL;
  	Node *funcexpr;
  	RangeTblEntry *rte;
  
***************
*** 1091,1096 **** ExplainScanTarget(Scan *plan, ExplainState *es)
--- 1402,1408 ----
  			/* Assert it's on a real relation */
  			Assert(rte->rtekind == RTE_RELATION);
  			objectname = get_rel_name(rte->relid);
+ 			objecttag = "Relation Name";
  			break;
  		case T_FunctionScan:
  			/* Assert it's on a RangeFunction */
***************
*** 1107,1112 **** ExplainScanTarget(Scan *plan, ExplainState *es)
--- 1419,1425 ----
  			{
  				Oid			funcid = ((FuncExpr *) funcexpr)->funcid;
  				objectname = get_func_name(funcid);
+ 				objecttag = "Function Name";
  			}
  			break;
  		case T_ValuesScan:
***************
*** 1117,1133 **** ExplainScanTarget(Scan *plan, ExplainState *es)
--- 1430,1456 ----
  			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;
  	}
  
+ 	if (es->format != EXPLAIN_FORMAT_TEXT)
+ 	{
+ 		if (objecttag != NULL)
+ 			ExplainPropertyText(objecttag, objectname, es);
+ 		ExplainPropertyText("Alias", rte->eref->aliasname, es);
+ 		return;
+ 	}
+ 
  	appendStringInfoString(es->str, " on");
  	if (objectname != NULL)
  		appendStringInfo(es->str, " %s", quote_identifier(objectname));
***************
*** 1151,1157 **** ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
  
  	foreach(lst, plans)
  	{
! 		ExplainNode((Plan *) lfirst(lst), planstate[j], outer_plan, NULL, es);
  		++j;
  	}
  }
--- 1474,1481 ----
  
  	foreach(lst, plans)
  	{
! 		ExplainNode((Plan *) lfirst(lst), planstate[j], outer_plan,
! 					"Member", NULL, es);
  		++j;
  	}
  }
***************
*** 1160,1166 **** ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
   * Explain a list of Subplans (or initPlans, which use SubPlan nodes).
   */
  static void
! ExplainSubNodes(List *plans, ExplainState *es)
  {
  	ListCell   *lst;
  
--- 1484,1490 ----
   * Explain a list of Subplans (or initPlans, which use SubPlan nodes).
   */
  static void
! ExplainSubNodes(List *plans, char *relationship, ExplainState *es)
  {
  	ListCell   *lst;
  
***************
*** 1170,1175 **** ExplainSubNodes(List *plans, ExplainState *es)
  		SubPlan    *sp = (SubPlan *) sps->xprstate.expr;
  
  		ExplainNode(exec_subplan_get_plan(es->pstmt, sp),
! 					sps->planstate, NULL, sp->plan_name, es);
  	}
  }
--- 1494,1670 ----
  		SubPlan    *sp = (SubPlan *) sps->xprstate.expr;
  
  		ExplainNode(exec_subplan_get_plan(es->pstmt, sp),
! 					sps->planstate, NULL, relationship, sp->plan_name, es);
! 	}
! }
! 
! /*
!  * Explain a property, such as sort keys or targets, that takes the form of
!  * a list.
!  */
! static void
! ExplainPropertyList(char *qlabel, List *data, ExplainState *es)
! {
! 	ListCell *lc;
! 
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 	{
! 		int first = 1;
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		appendStringInfo(es->str, "  %s: ", qlabel);
! 		foreach (lc, data)
! 		{
! 			if (!first)
! 				appendStringInfoString(es->str, ", ");
! 			appendStringInfoString(es->str, lfirst(lc));
! 			first = 0;
! 		}
! 		appendStringInfoChar(es->str, '\n');
! 	}
! 	else if (es->format == EXPLAIN_FORMAT_XML)
! 	{
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		ExplainXMLTag(qlabel, 0, es);
! 		appendStringInfo(es->str, "\n");
! 		foreach (lc, data)
! 		{
! 			appendStringInfoSpaces(es->str, es->indent * 2 + 2);
! 			appendStringInfoString(es->str, "<Item>");
! 			escape_xml(es->str, (const char *) lfirst(lc));
! 			appendStringInfoString(es->str, "</Item>\n");
! 		}
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		ExplainXMLTag(qlabel, 1, es);
! 		appendStringInfo(es->str, "\n");
! 	}
! 	else if (es->format == EXPLAIN_FORMAT_JSON)
! 	{
! 		int first = 1;
! 		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 = 0;
! 		}
! 		appendStringInfoChar(es->str, ']');
! 	}
! }
! 
! /*
!  * Explain text property.
!  */
! static void
! ExplainPropertyText(const char *qlabel, const char *s, ExplainState *es)
! {
! 	if (es->format == EXPLAIN_FORMAT_TEXT)
! 	{
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		appendStringInfo(es->str, "  %s: %s\n", qlabel, s);
! 	}
! 	else if (es->format == EXPLAIN_FORMAT_XML)
! 	{
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		ExplainXMLTag(qlabel, 0, es);
! 		escape_xml(es->str, s);
! 		ExplainXMLTag(qlabel, 1, es);
! 		appendStringInfoChar(es->str, '\n');
! 	}
! 	else if (es->format == EXPLAIN_FORMAT_JSON)
! 	{
! 		ExplainJSONLineEnding(es);
! 		appendStringInfoSpaces(es->str, es->indent * 2);
! 		escape_json(es->str, qlabel);
! 		appendStringInfoString(es->str, ": ");
! 		escape_json(es->str, s);
! 	}
! }
! 
! /*
!  * 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).  es->needs_separator is
!  * true for each property but the first.
!  */
! static void
! ExplainJSONLineEnding(ExplainState *es)
! {
! 	Assert(es->format == EXPLAIN_FORMAT_JSON);
! 	if (es->needs_separator)
! 		appendStringInfoChar(es->str, ',');
! 	appendStringInfoChar(es->str, '\n');
! 	es->needs_separator = 1;
! }
! 
! /*
!  * Emit opening or closing XML tag.  XML tag names can't contain white space,
!  * so we replace any spaces we encounter with dashes.
!  */
! static void
! ExplainXMLTag(const char *tagname, int closing, ExplainState *es)
! {
! 	const char *s;
! 
! 	appendStringInfoCharMacro(es->str, '<');
! 	if (closing)
! 		appendStringInfoCharMacro(es->str, '/');
! 	for (s = tagname; *s; ++s)
! 	{
! 		if (*s == ' ')
! 			appendStringInfoCharMacro(es->str, '-');
! 		else
! 			appendStringInfoCharMacro(es->str, *s);
! 	}
! 	appendStringInfoCharMacro(es->str, '>');
! }
! 
! /*
!  * Escape characters in text that have special meanings in JSON.
!  */
! 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 (*p < ' ')
! 					appendStringInfo(buf, "\\u%04x", (int) *p);
! 				else
! 					appendStringInfoCharMacro(buf, *p);
! 				break;
! 		}
  	}
+ 	appendStringInfoCharMacro(buf, '\"');
  }
*** a/src/backend/commands/prepare.c
--- b/src/backend/commands/prepare.c
***************
*** 720,726 **** ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
  
  		/* put a blank line between plans */
  		if (!is_last_query)
! 			appendStringInfoString(str, "\n");
  	}
  
  	if (estate)
--- 720,731 ----
  
  		/* put a blank line between plans */
  		if (!is_last_query)
! 		{
! 			if (stmt->format == EXPLAIN_FORMAT_TEXT)
! 				appendStringInfoChar(str, '\n');
! 			else if (stmt->format == EXPLAIN_FORMAT_JSON)
! 				appendStringInfoChar(str, ',');
! 		}
  	}
  
  	if (estate)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2876,2881 **** _copyExplainStmt(ExplainStmt *from)
--- 2876,2882 ----
  	COPY_NODE_FIELD(query);
  	COPY_SCALAR_FIELD(verbose);
  	COPY_SCALAR_FIELD(analyze);
+ 	COPY_SCALAR_FIELD(format);
  
  	return newnode;
  }
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1468,1473 **** _equalExplainStmt(ExplainStmt *a, ExplainStmt *b)
--- 1468,1474 ----
  	COMPARE_NODE_FIELD(query);
  	COMPARE_SCALAR_FIELD(verbose);
  	COMPARE_SCALAR_FIELD(analyze);
+ 	COMPARE_SCALAR_FIELD(format);
  
  	return true;
  }
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 405,410 **** makeExplain(List *options, Node *query)
--- 405,425 ----
  			n->analyze = parseBooleanOption(opt);
  		else if (!strcmp(opt->defname, "verbose"))
  			n->verbose = parseBooleanOption(opt);
+ 		else if (!strcmp(opt->defname, "format")) {
+ 			/* Currently, generic_option_arg can only be a String. */
+ 			Assert(IsA(opt->arg, String));
+ 			if (!strcmp(strVal(opt->arg), "text"))
+ 				n->format = EXPLAIN_FORMAT_TEXT;
+ 			else if (!strcmp(strVal(opt->arg), "xml"))
+ 				n->format = EXPLAIN_FORMAT_XML;
+ 			else if (!strcmp(strVal(opt->arg), "json"))
+ 				n->format = EXPLAIN_FORMAT_JSON;
+ 			else
+ 				ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("invalid value for parameter \"%s\"",
+ 						opt->defname)));
+ 		}
  		else
  			ereport(ERROR,
  				(errcode(ERRCODE_UNDEFINED_PARAMETER),
*** a/src/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 44,49 **** extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  			   StringInfo str);
  
  extern void ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
! 				 bool analyze, bool verbose);
  
  #endif   /* EXPLAIN_H */
--- 44,49 ----
  			   StringInfo str);
  
  extern void ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
! 				 bool analyze, bool verbose, ExplainFormat format);
  
  #endif   /* EXPLAIN_H */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2187,2198 **** typedef struct VacuumStmt
--- 2187,2206 ----
   *		Explain Statement
   * ----------------------
   */
+ typedef enum ExplainFormat
+ {
+ 	EXPLAIN_FORMAT_TEXT = 0,
+ 	EXPLAIN_FORMAT_XML = 1,
+ 	EXPLAIN_FORMAT_JSON = 2
+ } ExplainFormat;
+ 
  typedef struct ExplainStmt
  {
  	NodeTag		type;
  	Node	   *query;			/* the query (as a raw parse tree) */
  	bool		verbose;		/* print plan info */
  	bool		analyze;		/* get statistics by executing plan */
+ 	ExplainFormat	format;			/* desired output format */
  } ExplainStmt;
  
  /* ----------------------
#2Josh Berkus
josh@agliodbs.com
In reply to: Robert Haas (#1)
Re: machine-readable explain output

On 6/11/09 10:15 PM, Robert Haas wrote:

Here we go, XML and JSON output.

You will need to apply explain_refactor-v4.patch and
explain_options-v2.patch first, then apply the two patches attached to
this message.

Wow, cool. Can this work with auto_explain? That's where I see
machine-readable being most useful.

--
Josh Berkus
PostgreSQL Experts Inc.
www.pgexperts.com

#3Robert Haas
robertmhaas@gmail.com
In reply to: Josh Berkus (#2)
Re: machine-readable explain output

On Fri, Jun 12, 2009 at 11:47 AM, Josh Berkus<josh@agliodbs.com> wrote:

On 6/11/09 10:15 PM, Robert Haas wrote:

Here we go, XML and JSON output.

You will need to apply explain_refactor-v4.patch and
explain_options-v2.patch first, then apply the two patches attached to
this message.

Wow, cool.  Can this work with auto_explain?  That's where I see
machine-readable being most useful.

The patch does touch contrib/auto_explain, but just enough to make it
keep working the same way it does now. I don't think it would be too
hard to improve on that, though; I might work on it if I get bored,
but I'm hoping someone else will be motivated enough to do that part.
:-)

How would you go about extracting the XML/JSON bits from the rest of
what is in the log file? (apologies if this is a question I should
already know the answer to)

...Robert

#4Josh Berkus
josh@agliodbs.com
In reply to: Robert Haas (#3)
Re: machine-readable explain output

How would you go about extracting the XML/JSON bits from the rest of
what is in the log file? (apologies if this is a question I should
already know the answer to)

If you do CSV output, it's in a field.

--
Josh Berkus
PostgreSQL Experts Inc.
www.pgexperts.com

#5Andrew Dunstan
andrew@dunslane.net
In reply to: Josh Berkus (#4)
Re: machine-readable explain output

Josh Berkus wrote:

How would you go about extracting the XML/JSON bits from the rest of
what is in the log file? (apologies if this is a question I should
already know the answer to)

If you do CSV output, it's in a field.

And even if it's not, a well formed XML document would be fairly easy to
extract, especially if the root element is well known (e.g. <pg:explain>
or some such). And if the patch doesn't produce a well-formed XML doc
then it needs work ;-). It might be nice if Robert were to post some
samples of the output.

Quick question: does the patch use formal methods using, say, the DOM
API to build up the XML, or informal methods (like
foo.append('<element>'); foo.append(content); foo.append('</element>'); )

As you can tell, I haven't looked over it yet. But I intend to ;-)

cheers

andrew

#6Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#5)
Re: machine-readable explain output

On Fri, Jun 12, 2009 at 5:13 PM, Andrew Dunstan<andrew@dunslane.net> wrote:

Josh Berkus wrote:

How would you go about extracting the XML/JSON bits from the rest of
what is in the log file?  (apologies if this is a question I should
already know the answer to)

If you do CSV output, it's in a field.

And even if it's not, a well formed XML document would be fairly easy to
extract, especially if the root element is well known (e.g. <pg:explain> or
some such). And if the patch doesn't produce a well-formed XML doc then it
needs work ;-). It might be nice if Robert were to post some samples of the
output.

<pgexplain>, as it happens... I could post some samples of the
output, but it seems like it might be just as well to let those who
are curious try it for themselves. I'd rather get opinions from
people who care enough to download & test than from those who are just
bikeshedding. :-)

Quick question: does the patch use formal methods using, say, the DOM API to
build up the XML, or informal methods  (like foo.append('<element>');
foo.append(content); foo.append('</element>'); )

As you can tell, I haven't looked over it yet. But I intend to ;-)

Use the Source, Luke. :-)

But, it's informal methods. I don't see a lot of value in doing it
the other way, though perhaps I could be convinced otherwise. One
thing that's nice about the way it works now is that the only support
function it requires is a basic XML-escaping function, which it turns
out we already have in the PG sources anyway, though not in a quite
usable form (the infrastructure patch deals with the necessary
adjustments). So you can explain (format xml) even if you compile
without --with-libxml.

If you want to see how the actual XML/JSON stuff works, you might want
to start with the last patch in the series (explain_format). If you
want to commit it, a course of action to which I can give my unbiased
endorsement, then you'll want to start with explain_refactor.

...Robert

#7Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#1)
Re: machine-readable explain output

On Friday 12 June 2009 08:15:17 Robert Haas wrote:

Here we go, XML and JSON output.

Could you post some examples of how some plans would look in either format?
That would help us judge the particulars.

#8Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#6)
Re: machine-readable explain output

On Saturday 13 June 2009 01:10:06 Robert Haas wrote:

<pgexplain>, as it happens... I could post some samples of the
output, but it seems like it might be just as well to let those who
are curious try it for themselves. I'd rather get opinions from
people who care enough to download & test than from those who are just
bikeshedding. :-)

I recommend, however, that you think about writing a regression test for this,
so the interfaces are explicit, and those tweaking them in the future know
what they are dealing with.

A couple of comments on the specifics of the output:

For the JSON format:

* Numbers should not be quoted.

For the XML format:

* Instead of <pgexplain>, use <explain> with an XML namespace declaration.

The schema name is missing in either output format. I think that was supposed
to be one of the features of this that the objects are unambiguously
qualified.

I'm not sure I like element names such as <Node-Type>, instead of say
<nodetype>, which is more like HTML and DocBook. (Your way might be more like
SOAP, I guess.)

Also, the result type of an EXPLAIN (format xml) should be type xml, not text.

In general, I like this direction very much. There will probably be more
tweaks on the output format over time. It's not like the plain EXPLAIN hasn't
been tweaked countless times.

#9Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#8)
Re: machine-readable explain output

On Sat, Jun 13, 2009 at 9:08 AM, Peter Eisentraut<peter_e@gmx.net> wrote:

On Saturday 13 June 2009 01:10:06 Robert Haas wrote:

<pgexplain>, as it happens...  I could post some samples of the
output, but it seems like it might be just as well to let those who
are curious try it for themselves.  I'd rather get opinions from
people who care enough to download & test than from those who are just
bikeshedding.  :-)

I recommend, however, that you think about writing a regression test for this,
so the interfaces are explicit, and those tweaking them in the future know
what they are dealing with.

I would like to have something in this area, but Tom didn't think it
was workable.

http://archives.postgresql.org/message-id/603c8f070904151623ne07d744k615edd4aa669a64a@mail.gmail.com

Currently, we don't even have something trivial like "EXPLAIN SELECT
1" in the regression tests, so even if you completely break EXPLAIN so
that it core dumps (voice of experience speaking here) make check
still passes with flying colors.

One feature I'd like to add is an EXPLAIN-option for "COSTS", so that
you can say explain (costs off) .... Then we could at least try a
couple of simple examples against the build-farm to see whether the
issues that Tom is worried about are problems in practice and to what
degree. But I'm a little reluctant to develop that until at least
some of my existing work is committed, because at present I have no
guarantee either that this patch will be accepted or that it won't be
extensively modified by the committer, thus creating merge conflicts
for me to resolve. However, assuming the infrastructure in the
explain_options patch is accepted in something similar to its current
form, it should be a very easy patch to write when the time comes.

A couple of comments on the specifics of the output:

For the JSON format:

* Numbers should not be quoted.

OK, will fix.

For the XML format:

* Instead of <pgexplain>, use <explain> with an XML namespace declaration.

Could you specify this a bit further, like write out exactly what you
want it to look like? My XML-fu is not very strong.

The schema name is missing in either output format.  I think that was supposed
to be one of the features of this that the objects are unambiguously
qualified.

Well, as I said, I'm not sure that this decision should be made based
on the selected output format. I think it should be controlled by a
separate option that can be used for text, XML, or JSON. Of course,
we also don't want to end up with a zillion options. I think maybe
the existing VERBOSE option could be pressed into service here. Right
now, all it does is print out the output lists for each node, but
maybe it could also have the effect of forcing the schema name to be
emitted, and any other similarly minor verbosities we run across.

There's other weirdness in this area too: when emitting a qual, we
table-qualify column names according to a complex heuristic (for scan
quals, when the outer plan is non-NULL or it's a subquery scan; for
upper quals, when the length of the range-table list is more than 1).
Not sure whether anyone cares about this or not. In a similar vein,
in report_triggers(), we omit the constraint name if there is a
trigger name. All of these seem like fairly good candidates for
things that you might want to behave differently if you ask for
"VERBOSE".

I'm not sure I like element names such as <Node-Type>, instead of say
<nodetype>, which is more like HTML and DocBook.  (Your way might be more like
SOAP, I guess.)

I'm not sure I like them either. I mostly did it that way because I
wanted to maintain consistency with the text output, which uses labels
like "Hash Cond" and "Filter". So I just made the JSON format use
those same labels, and for the XML format, since tag names can't
contain spaces, I just replaced spaces with dashes. Once I made that
decision it seemed like everything else should be consistent, so
that's what I did. But we could certainly subject them all to some
additional regular transformation if we're so inclined. I'm not sure
it's really worth the additional code complexity, but I don't care
very much.

Also, the result type of an EXPLAIN (format xml) should be type xml, not text.

Seems reasonable. I'll see if I can figure out how to do that.

In general, I like this direction very much.  There will probably be more
tweaks on the output format over time.  It's not like the plain EXPLAIN hasn't
been tweaked countless times.

Cool, thanks for the review. I have no illusions it won't get changed
further. In all honesty, I'm most interested in the options syntax.
The multiple output formats portion is just a demonstration that you
can use the options syntax to enable interesting functionality, but I
personally have little use for it. I'm hoping, however, that once we
have a standard way to add options, people will propose more options
that do interesting things; I have a few ideas myself.

...Robert

#10Bernd Helmle
mailings@oopsware.de
In reply to: Robert Haas (#9)
Re: machine-readable explain output

--On 13. Juni 2009 15:01:43 -0400 Robert Haas <robertmhaas@gmail.com> wrote:

Also, the result type of an EXPLAIN (format xml) should be type xml, not
text.

Seems reasonable. I'll see if I can figure out how to do that.

I suppose it's okay then, that the format is not available when the server
isn't build with --with-libxml ?

--
Thanks

Bernd

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bernd Helmle (#10)
Re: machine-readable explain output

Bernd Helmle <mailings@oopsware.de> writes:

--On 13. Juni 2009 15:01:43 -0400 Robert Haas <robertmhaas@gmail.com> wrote:

Also, the result type of an EXPLAIN (format xml) should be type xml, not
text.

Seems reasonable. I'll see if I can figure out how to do that.

I suppose it's okay then, that the format is not available when the server
isn't build with --with-libxml ?

I believe we have things set up so that you can still print "xml" data
without libxml configured in. We'd need to be sure casting to text
works too, but other than that I don't see an issue here.

regards, tom lane

#12Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#11)
Re: machine-readable explain output

On Sat, Jun 13, 2009 at 6:40 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Bernd Helmle <mailings@oopsware.de> writes:

--On 13. Juni 2009 15:01:43 -0400 Robert Haas <robertmhaas@gmail.com> wrote:

Also, the result type of an EXPLAIN (format xml) should be type xml, not
text.

Seems reasonable.  I'll see if I can figure out how to do that.

I suppose it's okay then, that the format is not available when the server
isn't build with --with-libxml ?

I believe we have things set up so that you can still print "xml" data
without libxml configured in.  We'd need to be sure casting to text
works too, but other than that I don't see an issue here.

Hmm, I just tried to do this by modifying ExplainResultDesc to use
XMLOID rather than TEXTOID when stmt->format == EXPLAIN_FORMAT_XML,
and sure enough, explain (format xml) ... fails when --with-libxml is
not specified. But maybe that's not the right way to do it - now that
I think about it, using that in combination with
do_text_output_multiline() seems totally wrong even if we end up
deciding not to worry about the output type, since while there are
multiple rows when the output is considered as text, there is surely
only one row when you look at the whole thing as an XML document. I'm
not too sure how to do this though. Help?

In any event, considering that EXPLAIN is a utility statement and
can't be embedded within a query, I'm not sure what benefit we get out
of returning the data as XML rather than text. This doesn't seem
likely to change either, based on Tom's comments here.

http://archives.postgresql.org/pgsql-hackers/2009-05/msg00969.php

...Robert

#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#12)
Re: machine-readable explain output

Robert Haas <robertmhaas@gmail.com> writes:

In any event, considering that EXPLAIN is a utility statement and
can't be embedded within a query, I'm not sure what benefit we get out
of returning the data as XML rather than text. This doesn't seem
likely to change either, based on Tom's comments here.

http://archives.postgresql.org/pgsql-hackers/2009-05/msg00969.php

I think you misinterpreted the point of that example, which is that
there already is a way to get the output of EXPLAIN into the system
for further processing. Were this not so, we wouldn't be worrying
at all what data type it claims to have. But since there is a way,
it's important what data type it produces.

regards, tom lane

#14Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#13)
Re: machine-readable explain output

On Sat, Jun 13, 2009 at 7:42 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

In any event, considering that EXPLAIN is a utility statement and
can't be embedded within a query, I'm not sure what benefit we get out
of returning the data as XML rather than text.  This doesn't seem
likely to change either, based on Tom's comments here.

http://archives.postgresql.org/pgsql-hackers/2009-05/msg00969.php

I think you misinterpreted the point of that example, which is that
there already is a way to get the output of EXPLAIN into the system
for further processing.  Were this not so, we wouldn't be worrying
at all what data type it claims to have.  But since there is a way,
it's important what data type it produces.

Well, if you get the EXPLAIN output into the system by defining a
wrapper function, said wrapper function will return the type that it's
defined to return, regardless of what EXPLAIN itself returns, no?

I don't have a problem making it return XML; I'm just not exactly sure
how to do it. Is it possible to get that working without depending on
libxml? How?

...Robert

#15Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#9)
Re: machine-readable explain output

On Saturday 13 June 2009 22:01:43 Robert Haas wrote:

* Instead of <pgexplain>, use <explain> with an XML namespace
declaration.

Could you specify this a bit further, like write out exactly what you
want it to look like? My XML-fu is not very strong.

Just replace your <pgexplain> by

<explain xmlns="http://www.postgresql.org/2009/explain&quot;&gt;

The actual URI doesn't matter, as long as it is distinguishing. The value I
chose here follows conventions used by W3C.

#16Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#9)
Re: machine-readable explain output

On Saturday 13 June 2009 22:01:43 Robert Haas wrote:

I recommend, however, that you think about writing a regression test for
this, so the interfaces are explicit, and those tweaking them in the
future know what they are dealing with.

I would like to have something in this area, but Tom didn't think it
was workable.

http://archives.postgresql.org/message-id/603c8f070904151623ne07d744k615edd
4aa669a64a@mail.gmail.com

Currently, we don't even have something trivial like "EXPLAIN SELECT
1" in the regression tests, so even if you completely break EXPLAIN so
that it core dumps (voice of experience speaking here) make check
still passes with flying colors.

That post described a scenario where you check whether given a data set and
ANALYZE, the optimizer produces a certain plan. I agree that that might be
tricky.

A regression test for EXPLAIN, however, should primarily check whether the
output format is stable. We are planning to offer this as a public interface,
after all. You could use faked up statistics and all but one or two plan
types turned off, and then the results should be pretty stable. Unless the
fundamental cost model changes, but it doesn't do that very often for the
simpler plan types anyway. Things to check for would be checking whether all
the fields are there, quoted and escaped correctly, and what happens if
statistics are missing or corrupted, etc. Or whether you get any output at
all, as you say.

#17Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#14)
Re: machine-readable explain output

On Sunday 14 June 2009 07:27:19 Robert Haas wrote:

On Sat, Jun 13, 2009 at 7:42 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

In any event, considering that EXPLAIN is a utility statement and
can't be embedded within a query, I'm not sure what benefit we get out
of returning the data as XML rather than text. This doesn't seem
likely to change either, based on Tom's comments here.

http://archives.postgresql.org/pgsql-hackers/2009-05/msg00969.php

I think you misinterpreted the point of that example, which is that
there already is a way to get the output of EXPLAIN into the system
for further processing. Were this not so, we wouldn't be worrying
at all what data type it claims to have. But since there is a way,
it's important what data type it produces.

Well, if you get the EXPLAIN output into the system by defining a
wrapper function, said wrapper function will return the type that it's
defined to return, regardless of what EXPLAIN itself returns, no?

I don't have a problem making it return XML; I'm just not exactly sure
how to do it. Is it possible to get that working without depending on
libxml? How?

Even if this doesn't end up being feasible, I feel it's important that the XML
and JSON formats return one datum, not one per line. Otherwise a client that
wants to do some processing on the result will have to do about three extra
steps to get the result usable.

#18Dave Page
dpage@pgadmin.org
In reply to: Bernd Helmle (#10)
Re: machine-readable explain output

On 6/13/09, Bernd Helmle <mailings@oopsware.de> wrote:

--On 13. Juni 2009 15:01:43 -0400 Robert Haas <robertmhaas@gmail.com> wrote:

Also, the result type of an EXPLAIN (format xml) should be type xml, not
text.

Seems reasonable. I'll see if I can figure out how to do that.

I suppose it's okay then, that the format is not available when the server
isn't build with --with-libxml ?

I hope not, otherwise the usefulness of the format is significantly
reduced (to practically zero) if tools cannot rely on it being
available and have to fall back to something else if it's not
available.

--
Dave Page
EnterpriseDB UK: http://www.enterprisedb.com

#19Pavel Stehule
pavel.stehule@gmail.com
In reply to: Dave Page (#18)
Re: machine-readable explain output

2009/6/14 Dave Page <dpage@pgadmin.org>:

On 6/13/09, Bernd Helmle <mailings@oopsware.de> wrote:

--On 13. Juni 2009 15:01:43 -0400 Robert Haas <robertmhaas@gmail.com> wrote:

Also, the result type of an EXPLAIN (format xml) should be type xml, not
text.

Seems reasonable.  I'll see if I can figure out how to do that.

I suppose it's okay then, that the format is not available when the server
isn't build with --with-libxml ?

I hope not, otherwise the usefulness of the format is significantly
reduced (to practically zero) if tools cannot rely on it being
available and have to fall back to something else if it's not
available.

I thing so using --with-libxml is good idea. Is nonsense repeat some
necessary xml code like xml escaping and similar. And almost all
distributed PostgreSQL binaries are compiled with xml support, so this
cannot do some problems. When somebody compile pg without xml support,
then he knows what he do.

regards
Pavel Stehule

Show quoted text

--
Dave Page
EnterpriseDB UK:   http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Dave Page
dpage@pgadmin.org
In reply to: Pavel Stehule (#19)
Re: machine-readable explain output

On 6/14/09, Pavel Stehule <pavel.stehule@gmail.com> wrote:

2009/6/14 Dave Page <dpage@pgadmin.org>:

On 6/13/09, Bernd Helmle <mailings@oopsware.de> wrote:

--On 13. Juni 2009 15:01:43 -0400 Robert Haas <robertmhaas@gmail.com>
wrote:

Also, the result type of an EXPLAIN (format xml) should be type xml,
not
text.

Seems reasonable. I'll see if I can figure out how to do that.

I suppose it's okay then, that the format is not available when the
server
isn't build with --with-libxml ?

I hope not, otherwise the usefulness of the format is significantly
reduced (to practically zero) if tools cannot rely on it being
available and have to fall back to something else if it's not
available.

I thing so using --with-libxml is good idea. Is nonsense repeat some
necessary xml code like xml escaping and similar. And almost all
distributed PostgreSQL binaries are compiled with xml support, so this
cannot do some problems. When somebody compile pg without xml support,
then he knows what he do.

That will mean we never get to use XML explain in pgAdmin. We're not
in the business of writing basic features that might work, if the
postgres packager enabled an option. We need to be able to rely on
such features always being available.

--
Dave Page
EnterpriseDB UK: http://www.enterprisedb.com

#21Andrew Dunstan
andrew@dunslane.net
In reply to: Dave Page (#20)
Re: machine-readable explain output

Dave Page wrote:

I thing so using --with-libxml is good idea. Is nonsense repeat some
necessary xml code like xml escaping and similar. And almost all
distributed PostgreSQL binaries are compiled with xml support, so this
cannot do some problems. When somebody compile pg without xml support,
then he knows what he do.

That will mean we never get to use XML explain in pgAdmin. We're not
in the business of writing basic features that might work, if the
postgres packager enabled an option. We need to be able to rely on
such features always being available.

As a matter of curiosity, do we have any idea what platforms don't
support libxml2?

cheers

andrew

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dave Page (#20)
Re: machine-readable explain output

Dave Page <dpage@pgadmin.org> writes:

On 6/14/09, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I thing so using --with-libxml is good idea.

That will mean we never get to use XML explain in pgAdmin.

Exactly. We are *not* going to make libxml a required piece of
infrastructure, and that means that XML-format explain output will
be useless to most client tools if it doesn't work without libxml.

regards, tom lane

#23Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#21)
Re: machine-readable explain output

Andrew Dunstan <andrew@dunslane.net> writes:

As a matter of curiosity, do we have any idea what platforms don't
support libxml2?

It's only partially about whether libxml2 is portable enough. A person
building Postgres might also have legitimate concerns about how bug-free
and/or secure it is. We've already spent nontrivial amounts of time
working around libxml bugs; and as for security, google shows at least
four CVEs against libxml2 in the past two years, so it's not a
negligible risk. I can entirely see people choosing to build without it.

regards, tom lane

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#16)
Re: machine-readable explain output

Peter Eisentraut <peter_e@gmx.net> writes:

A regression test for EXPLAIN, however, should primarily check whether the
output format is stable. We are planning to offer this as a public interface,
after all. You could use faked up statistics and all but one or two plan
types turned off, and then the results should be pretty stable.

You'd be surprised :-(. We've found in the past that queries in the
regression tests get different plans across different platforms just
because of alignment-rule differences (leading to different numbers
of rows per page, etc etc). I think that test cases could be chosen
to be relatively stable points in the plan space, but it's hopeless
to imagine that the low-order digits of cost estimates will be the
same across all platforms.

regards, tom lane

#25Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#12)
Re: machine-readable explain output

Robert Haas <robertmhaas@gmail.com> writes:

On Sat, Jun 13, 2009 at 6:40 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

I believe we have things set up so that you can still print "xml" data
without libxml configured in. �We'd need to be sure casting to text
works too, but other than that I don't see an issue here.

Hmm, I just tried to do this by modifying ExplainResultDesc to use
XMLOID rather than TEXTOID when stmt->format == EXPLAIN_FORMAT_XML,
and sure enough, explain (format xml) ... fails when --with-libxml is
not specified.

That's because the code goes through BuildTupleFromCStrings, which
invokes xml_in in this scenario, and xml_in (as opposed to xml_out)
does depend on libxml.

However, using BuildTupleFromCStrings is wasteful/stupid for *both*
text and xml output, so it seems like getting rid of it is the thing
to do here.

regards, tom lane

#26Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#25)
Re: machine-readable explain output

On Sun, Jun 14, 2009 at 11:28 AM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Sat, Jun 13, 2009 at 6:40 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

I believe we have things set up so that you can still print "xml" data
without libxml configured in.  We'd need to be sure casting to text
works too, but other than that I don't see an issue here.

Hmm, I just tried to do this by modifying ExplainResultDesc to use
XMLOID rather than TEXTOID when stmt->format == EXPLAIN_FORMAT_XML,
and sure enough, explain (format xml) ... fails when --with-libxml is
not specified.

That's because the code goes through BuildTupleFromCStrings, which
invokes xml_in in this scenario, and xml_in (as opposed to xml_out)
does depend on libxml.

However, using BuildTupleFromCStrings is wasteful/stupid for *both*
text and xml output, so it seems like getting rid of it is the thing
to do here.

Makes sense. However, if we just make that change in do_tup_output(),
then we'll break the ability to use that function for non-text
datatypes. Currently that doesn't look like a problem, because the
only clients are ShowGUCConfigOption(), do_text_output_oneline(), and
do_text_output_multiline(),

#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#26)
Re: machine-readable explain output

Robert Haas <robertmhaas@gmail.com> writes:

On Sun, Jun 14, 2009 at 11:28 AM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

However, using BuildTupleFromCStrings is wasteful/stupid for *both*
text and xml output, so it seems like getting rid of it is the thing
to do here.

Makes sense. However, if we just make that change in do_tup_output(),
then we'll break the ability to use that function for non-text
datatypes.

I'd envision it taking Datums, so it doesn't really matter. However,
as you say, specializing it to text only wouldn't be much of a loss.

regards, tom lane

#28Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#27)
Re: machine-readable explain output

On Sun, Jun 14, 2009 at 1:02 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Sun, Jun 14, 2009 at 11:28 AM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

However, using BuildTupleFromCStrings is wasteful/stupid for *both*
text and xml output, so it seems like getting rid of it is the thing
to do here.

Makes sense.  However, if we just make that change in do_tup_output(),
then we'll break the ability to use that function for non-text
datatypes.

I'd envision it taking Datums, so it doesn't really matter.  However,
as you say, specializing it to text only wouldn't be much of a loss.

I like the Datum option, so I'll work up a patch for that, unless you
want to just do it and spare me the trouble. :-)

...Robert

#29Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#28)
1 attachment(s)
Re: machine-readable explain output

On Sun, Jun 14, 2009 at 1:04 PM, Robert Haas<robertmhaas@gmail.com> wrote:

On Sun, Jun 14, 2009 at 1:02 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Sun, Jun 14, 2009 at 11:28 AM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

However, using BuildTupleFromCStrings is wasteful/stupid for *both*
text and xml output, so it seems like getting rid of it is the thing
to do here.

Makes sense.  However, if we just make that change in do_tup_output(),
then we'll break the ability to use that function for non-text
datatypes.

I'd envision it taking Datums, so it doesn't really matter.  However,
as you say, specializing it to text only wouldn't be much of a loss.

I like the Datum option, so I'll work up a patch for that, unless you
want to just do it and spare me the trouble.  :-)

Here's an attempt. Is this anything like what you had in mind?

...Robert

Attachments:

do_tup_output_datum-v1.patchtext/x-diff; charset=US-ASCII; name=do_tup_output_datum-v1.patchDownload
*** a/src/backend/executor/execTuples.c
--- b/src/backend/executor/execTuples.c
***************
*** 95,100 ****
--- 95,101 ----
  #include "catalog/pg_type.h"
  #include "nodes/nodeFuncs.h"
  #include "storage/bufmgr.h"
+ #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/typcache.h"
  
***************
*** 1216,1232 **** begin_tup_output_tupdesc(DestReceiver *dest, TupleDesc tupdesc)
  /*
   * write a single tuple
   *
-  * values is a list of the external C string representations of the values
-  * to be projected.
-  *
   * XXX This could be made more efficient, since in reality we probably only
   * need a virtual tuple.
   */
  void
! do_tup_output(TupOutputState *tstate, char **values)
  {
! 	/* build a tuple from the input strings using the tupdesc */
! 	HeapTuple	tuple = BuildTupleFromCStrings(tstate->metadata, values);
  
  	/* put it in a slot */
  	ExecStoreTuple(tuple, tstate->slot, InvalidBuffer, true);
--- 1217,1233 ----
  /*
   * write a single tuple
   *
   * XXX This could be made more efficient, since in reality we probably only
   * need a virtual tuple.
   */
  void
! do_tup_output(TupOutputState *tstate, Datum *values, bool *isnull)
  {
! 	TupleDesc	tupdesc = tstate->metadata->tupdesc;
! 	HeapTuple	tuple;
! 
! 	/* Form a tuple. */
! 	tuple = heap_form_tuple(tupdesc, values, isnull);
  
  	/* put it in a slot */
  	ExecStoreTuple(tuple, tstate->slot, InvalidBuffer, true);
***************
*** 1241,1264 **** do_tup_output(TupOutputState *tstate, char **values)
  /*
   * write a chunk of text, breaking at newline characters
   *
-  * NB: scribbles on its input!
-  *
   * Should only be used with a single-TEXT-attribute tupdesc.
   */
  void
  do_text_output_multiline(TupOutputState *tstate, char *text)
  {
  	while (*text)
  	{
  		char	   *eol;
  
  		eol = strchr(text, '\n');
  		if (eol)
! 			*eol++ = '\0';
  		else
! 			eol = text +strlen(text);
  
! 		do_tup_output(tstate, &text);
  		text = eol;
  	}
  }
--- 1242,1275 ----
  /*
   * write a chunk of text, breaking at newline characters
   *
   * Should only be used with a single-TEXT-attribute tupdesc.
   */
  void
  do_text_output_multiline(TupOutputState *tstate, char *text)
  {
+ 	Datum		values[1];
+ 	bool		isnull[1] = { false };
+ 
  	while (*text)
  	{
  		char	   *eol;
+ 		int			len;
  
  		eol = strchr(text, '\n');
  		if (eol)
! 		{
! 			len = eol - text;
! 			++eol;
! 		}
  		else
! 		{
! 			len = strlen(text);
! 			eol += len;
! 		}
  
! 		values[0] = PointerGetDatum(cstring_to_text_with_len(text, len));
! 		do_tup_output(tstate, values, isnull);
! 		pfree(DatumGetPointer(values[0]));
  		text = eol;
  	}
  }
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
***************
*** 5978,5984 **** ShowAllGUCConfig(DestReceiver *dest)
  	int			i;
  	TupOutputState *tstate;
  	TupleDesc	tupdesc;
! 	char	   *values[3];
  
  	/* need a tuple descriptor representing three TEXT columns */
  	tupdesc = CreateTemplateTupleDesc(3, false);
--- 5978,5985 ----
  	int			i;
  	TupOutputState *tstate;
  	TupleDesc	tupdesc;
! 	Datum	    values[3];
! 	bool		isnull[3] = { false, false, false };
  
  	/* need a tuple descriptor representing three TEXT columns */
  	tupdesc = CreateTemplateTupleDesc(3, false);
***************
*** 5996,6017 **** ShowAllGUCConfig(DestReceiver *dest)
  	for (i = 0; i < num_guc_variables; i++)
  	{
  		struct config_generic *conf = guc_variables[i];
  
  		if ((conf->flags & GUC_NO_SHOW_ALL) ||
  			((conf->flags & GUC_SUPERUSER_ONLY) && !am_superuser))
  			continue;
  
  		/* assign to the values array */
! 		values[0] = (char *) conf->name;
! 		values[1] = _ShowOption(conf, true);
! 		values[2] = (char *) conf->short_desc;
  
  		/* send it to dest */
! 		do_tup_output(tstate, values);
  
  		/* clean up */
! 		if (values[1] != NULL)
! 			pfree(values[1]);
  	}
  
  	end_tup_output(tstate);
--- 5997,6033 ----
  	for (i = 0; i < num_guc_variables; i++)
  	{
  		struct config_generic *conf = guc_variables[i];
+ 		char *setting;
  
  		if ((conf->flags & GUC_NO_SHOW_ALL) ||
  			((conf->flags & GUC_SUPERUSER_ONLY) && !am_superuser))
  			continue;
  
  		/* assign to the values array */
! 		values[0] = PointerGetDatum(cstring_to_text(conf->name));
! 		setting = _ShowOption(conf, true);
! 		if (setting)
! 		{
! 			values[1] = PointerGetDatum(cstring_to_text(setting));
! 			isnull[1] = false;
! 		}
! 		else {
! 			values[1] = PointerGetDatum(NULL);
! 			isnull[1] = true;
! 		}
! 		values[2] = PointerGetDatum(cstring_to_text(conf->short_desc));
  
  		/* send it to dest */
! 		do_tup_output(tstate, values, isnull);
  
  		/* clean up */
! 		pfree(DatumGetPointer(values[0]));
! 		if (setting != NULL)
! 		{
! 			pfree(setting);
! 			pfree(DatumGetPointer(values[1]));
! 		}
! 		pfree(DatumGetPointer(values[2]));
  	}
  
  	end_tup_output(tstate);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 231,237 **** typedef struct TupOutputState
  
  extern TupOutputState *begin_tup_output_tupdesc(DestReceiver *dest,
  						 TupleDesc tupdesc);
! extern void do_tup_output(TupOutputState *tstate, char **values);
  extern void do_text_output_multiline(TupOutputState *tstate, char *text);
  extern void end_tup_output(TupOutputState *tstate);
  
--- 231,238 ----
  
  extern TupOutputState *begin_tup_output_tupdesc(DestReceiver *dest,
  						 TupleDesc tupdesc);
! extern void do_tup_output(TupOutputState *tstate, Datum *dvalues,
! 							bool *isnull);
  extern void do_text_output_multiline(TupOutputState *tstate, char *text);
  extern void end_tup_output(TupOutputState *tstate);
  
***************
*** 242,250 **** extern void end_tup_output(TupOutputState *tstate);
   */
  #define do_text_output_oneline(tstate, text_to_emit) \
  	do { \
! 		char *values_[1]; \
! 		values_[0] = (text_to_emit); \
! 		do_tup_output(tstate, values_); \
  	} while (0)
  
  
--- 243,254 ----
   */
  #define do_text_output_oneline(tstate, text_to_emit) \
  	do { \
! 		Datum values_[1]; \
! 		bool isnull_[1]; \
! 		values_[0] = PointerGetDatum(cstring_to_text(text_to_emit)); \
! 		isnull_[0] = false; \
! 		do_tup_output(tstate, values_, isnull_); \
! 		pfree(DatumGetPointer(values_[0])); \
  	} while (0)
  
  
#30Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#29)
Re: machine-readable explain output

I wrote:

On Sun, Jun 14, 2009 at 1:04 PM, Robert Haas<robertmhaas@gmail.com> wrote:

On Sun, Jun 14, 2009 at 1:02 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Sun, Jun 14, 2009 at 11:28 AM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

However, using BuildTupleFromCStrings is wasteful/stupid for *both*
text and xml output, so it seems like getting rid of it is the thing
to do here.

Makes sense.  However, if we just make that change in do_tup_output(),
then we'll break the ability to use that function for non-text
datatypes.

I'd envision it taking Datums, so it doesn't really matter.  However,
as you say, specializing it to text only wouldn't be much of a loss.

I like the Datum option, so I'll work up a patch for that, unless you
want to just do it and spare me the trouble.  :-)

Here's an attempt.  Is this anything like what you had in mind?

Hmm... on further review, I'm thinking this is still a bit wastful,
because we don't really need (I think) to call
TupleDescGetAttInMetadata from begin_tup_output_tupdesc. But I'm not
sure what the best way is to avoid that. Any thoughts?

...Robert

#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#30)
Re: machine-readable explain output

Robert Haas <robertmhaas@gmail.com> writes:

Hmm... on further review, I'm thinking this is still a bit wastful,
because we don't really need (I think) to call
TupleDescGetAttInMetadata from begin_tup_output_tupdesc. But I'm not
sure what the best way is to avoid that. Any thoughts?

Er, just don't do it? We shouldn't need it if the function is doing
heap_form_tuple directly.

regards, tom lane

#32Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#31)
Re: machine-readable explain output

On Mon, Jun 15, 2009 at 9:51 AM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

Hmm... on further review, I'm thinking this is still a bit wastful,
because we don't really need (I think) to call
TupleDescGetAttInMetadata from begin_tup_output_tupdesc.  But I'm not
sure what the best way is to avoid that.  Any thoughts?

Er, just don't do it?  We shouldn't need it if the function is doing
heap_form_tuple directly.

Oh, I guess that works. I had thought there might be people calling
begin_tup_output_tupdesc() who wanted to go on to call
BuildTupleFromCStrings(), but it seems that's not the case. In fact,
it looks like I can probably rip that member out of TupOutputState
altogether.

Will update patch. Does this look like what you were thinking otherwise?

Thanks,

...Robert

#33Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#32)
Re: machine-readable explain output

Robert Haas <robertmhaas@gmail.com> writes:

it looks like I can probably rip that member out of TupOutputState
altogether.

Will update patch. Does this look like what you were thinking otherwise?

Yeah, that's exactly what I was thinking.

regards, tom lane

#34Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#1)
Re: machine-readable explain output

Hi,

On 06/12/2009 07:15 AM, Robert Haas wrote:

If you don't like the syntax, please argue about that on the "generic
explain options v2" thread. Let's try to use this thread to discuss
the output format, about which I spent a good deal of time agonizing.

I spent some time playing around with the explain output with various
queries. Beside the already raised mild dislike (from Peter Eisentraut I
think) of Upper-Case "-" seperated tag-names I found mainly one gripe:

<Startup-Cost>1710.98</Startup-Cost>
<Total-Cost>1710.98</Total-Cost>
<Plan-Rows>72398</Plan-Rows>
<Plan-Width>4</Plan-Width>
<Actual-Startup-Time>136.595</Actual-Startup-Time>
<Actual-Total-Time>136.595</Actual-Total-Time>
<Actual-Rows>72398</Actual-Rows>
<Actual-Loops>1</Actual-Loops>

This is a bit inconsistent. i.e. for the row estimate you use
<Plan-Rows/> and for <Startup-Cost/> you dont use the "Plan-" Prefix.
While for the 'analyze' generated variables you use the 'Actual-' prefix
consistently.

One approach would be to have two nodes like:
<Plan-Estimates>
<Startup-Cost>...</Startup-Cost>
...
</Plan-Estimates>
<Execution-Cost>
<Startup-Cost>...</Startup-Cost>
...
</Execution-Cost>

This would probably make it easier to write a future proof parser and it
also seems semantically sensible.

As an aside issue it would perhaps be nice (thinking of an
index-suggestion tool) to make it possible for having seperate estimates
on <Index-Cond> an <Filter> - In order not to change the format later
that perhaps has to be considered here.
Perhaps the current structure + some additional tags is also the best
here - I just noticed it being a potential issue.

Andres

#35Greg Stark
gsstark@mit.edu
In reply to: Andres Freund (#34)
Re: machine-readable explain output

On Tue, Jun 16, 2009 at 12:19 PM, Andres Freund<andres@anarazel.de> wrote:

<Startup-Cost>1710.98</Startup-Cost>
<Total-Cost>1710.98</Total-Cost>
<Plan-Rows>72398</Plan-Rows>
<Plan-Width>4</Plan-Width>
<Actual-Startup-Time>136.595</Actual-Startup-Time>
<Actual-Total-Time>136.595</Actual-Total-Time>
<Actual-Rows>72398</Actual-Rows>
<Actual-Loops>1</Actual-Loops>

XML's not really my thing currently but it sure seems strange to me to
have *everything* be a separate tag like this. Doesn't XML do
attributes too? I would have thought to use child tags like this only
for things that have some further structure.

I would have expected something like:

<join
<scan type=sequential source="foo.bar">
<estimates cost-startup=nnn cost-total=nnn rows=nnn width=nnn></>
<actual time-startup=nnn time-total=nnnn rows=nnn loops=nnn></>
</scan>
<scan type=function source="foo.bar($1)">
<parameters>
<parameter name="$1" expression="...."></>
</parameters>
</scan>
</join>

This would allow something like a graphical explain plan to still make
sense of a plan even if it finds a node it doesn't recognize. It would
still know generally what to do with a "scan" node or a "join" node
even if it is a new type of scan or join.

--
greg
http://mit.edu/~gsstark/resume.pdf

#36Andres Freund
andres@anarazel.de
In reply to: Greg Stark (#35)
Re: machine-readable explain output

On 06/16/2009 02:14 PM, Greg Stark wrote:

On Tue, Jun 16, 2009 at 12:19 PM, Andres Freund<andres@anarazel.de> wrote:

<Startup-Cost>1710.98</Startup-Cost>
<Total-Cost>1710.98</Total-Cost>
<Plan-Rows>72398</Plan-Rows>
<Plan-Width>4</Plan-Width>
<Actual-Startup-Time>136.595</Actual-Startup-Time>
<Actual-Total-Time>136.595</Actual-Total-Time>
<Actual-Rows>72398</Actual-Rows>
<Actual-Loops>1</Actual-Loops>

XML's not really my thing currently but it sure seems strange to me to
have *everything* be a separate tag like this. Doesn't XML do
attributes too? I would have thought to use child tags like this only
for things that have some further structure.

I would have expected something like:

<join
<scan type=sequential source="foo.bar">
<estimates cost-startup=nnn cost-total=nnn rows=nnn width=nnn></>
<actual time-startup=nnn time-total=nnnn rows=nnn loops=nnn></>
</scan>
<scan type=function source="foo.bar($1)">
<parameters>
<parameter name="$1" expression="...."></>
</parameters>
</scan>
</join>

This would allow something like a graphical explain plan to still make
sense of a plan even if it finds a node it doesn't recognize. It would
still know generally what to do with a "scan" node or a "join" node
even if it is a new type of scan or join.

While that also looks sensible the more structured variant makes it
easier to integrate additional stats which may not easily be pressed in
the 'attribute' format. As a fastly contrived example you could have io
statistics over time like:
<iostat>
<stat time="10" name=pagefault>...</stat>
<stat time="20" name=pagefault>...</stat>
<stat time="30" name=pagefault>...</stat>
</iostat>

Something like that would be harder with your variant.

Structuring it in tags like suggested above:
<Plan-Estimates>
<Startup-Cost>...</Startup-Cost>
...
</Plan-Estimates>
<Execution-Cost>
<Startup-Cost>...</Startup-Cost>
...
</Execution-Cost>

Enables displaying unknown 'scalar' values just like your variant and
also allows more structured values.

It would be interesting to get somebody having used the old explain in
an automated fashion into this discussion...

Andres

#37Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#36)
Re: machine-readable explain output

On Tue, Jun 16, 2009 at 8:53 AM, Andres Freund<andres@anarazel.de> wrote:

On 06/16/2009 02:14 PM, Greg Stark wrote:

On Tue, Jun 16, 2009 at 12:19 PM, Andres Freund<andres@anarazel.de>
 wrote:

<Startup-Cost>1710.98</Startup-Cost>
<Total-Cost>1710.98</Total-Cost>
<Plan-Rows>72398</Plan-Rows>
<Plan-Width>4</Plan-Width>
<Actual-Startup-Time>136.595</Actual-Startup-Time>
<Actual-Total-Time>136.595</Actual-Total-Time>
<Actual-Rows>72398</Actual-Rows>
<Actual-Loops>1</Actual-Loops>

XML's not really my thing currently but it sure seems strange to me to
have *everything* be a separate tag like this. Doesn't XML do
attributes too? I would have thought to use child tags like this only
for things that have some further structure.

I would have expected something like:

<join
    <scan type=sequential source="foo.bar">
        <estimates cost-startup=nnn cost-total=nnn rows=nnn width=nnn></>
        <actual time-startup=nnn time-total=nnnn rows=nnn loops=nnn></>
    </scan>
    <scan type=function source="foo.bar($1)">
        <parameters>
             <parameter name="$1" expression="...."></>
         </parameters>
     </scan>
</join>

This would allow something like a graphical explain plan to still make
sense of a plan even if it finds a node it doesn't recognize. It would
still know generally what to do with a "scan" node or a "join" node
even if it is a new type of scan or join.

As long as you understand how the current code uses <Plan> and
<Plans>, you can do this just as well with the current implementation.
Each plan node gets a <Plan>. If there are any plans "under" it, it
gets a <Plans> child which contains those. Whether you put the
additional details into attributes or other tags is irrelevant. As to
why I chose to do it this way, I had a couple of reasons:

1. It didn't seem very wise to go with the approach of trying to do
EVERYTHING with attributes. If I did that, then I'd either get really
long lines that were not easily readable, or I'd have to write some
kind of complicated line wrapping code (which didn't seem to make a
lot of sense for a machine-readable format). The current format isn't
the most beautiful thing I've ever seen, but you don't need a parser
to make sense of it, just a bit of patience.

2. I wanted the JSON output and the XML output to be similar, and that
seemed much easier with this design.

3. We have existing precedent for this design pattern in, e.g. table_to_xml

http://www.postgresql.org/docs/current/interactive/functions-xml.html

While that also looks sensible the more structured variant makes it easier
to integrate additional stats which may not easily be pressed in the
'attribute' format. As a fastly contrived example you could have io
statistics over time like:
<iostat>
  <stat time="10" name=pagefault>...</stat>
  <stat time="20" name=pagefault>...</stat>
  <stat time="30" name=pagefault>...</stat>
</iostat>

Something like that would be harder with your variant.

Structuring it in tags like suggested above:
<Plan-Estimates>
   <Startup-Cost>...</Startup-Cost>
   ...
</Plan-Estimates>
<Execution-Cost>
   <Startup-Cost>...</Startup-Cost>
   ...
</Execution-Cost>

Enables displaying unknown 'scalar' values just like your variant and also
allows more structured values.

It would be interesting to get somebody having used the old explain in an
automated fashion into this discussion...

Well, one problem with this is that the actual values are not costs,
but times, and the estimated values are not times, but costs. The
planner estimates the cost of operations on an arbitrary scale where
the cost of a sequential page fetch is 1.0. When we measure actual
times, they are in milliseconds. There is no point that I can see in
making it appear that those are the same thing. Observe the current
output:

explain analyze select 1;
QUERY PLAN
------------------------------------------------------------------------------------
Result (cost=0.00..0.01 rows=1 width=0) (actual time=0.005..0.007
rows=1 loops=1)
Total runtime: 0.243 ms
(2 rows)

...Robert

#38Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#37)
Re: machine-readable explain output

On 06/16/2009 03:22 PM, Robert Haas wrote:

Well, one problem with this is that the actual values are not costs,
but times, and the estimated values are not times, but costs. The
planner estimates the cost of operations on an arbitrary scale where
the cost of a sequential page fetch is 1.0. When we measure actual
times, they are in milliseconds. There is no point that I can see in
making it appear that those are the same thing. Observe the current
output:

Well - the aim was not to make it possible to use the same name for
"<plan-startup-cost>" and "<actual-startup-cost>" but to group them in
some way - so you can decide in some way (prefix or below a distinct
node) if they are related to planning or execution (And thus making it
easier to handle unknown tags).
That <actual-startup-time/> morphed into <startup-cost/> instead of
<startup-time> was just a typo.
Another solution would be to rename <Startup-Cost> into
<Plan-Startup-Cost> for consistency. But grouping them by some node
seems to be a bit more future-proof.

Andres

#39Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Haas (#37)
Re: machine-readable explain output

Robert Haas wrote:

3. We have existing precedent for this design pattern in, e.g. table_to_xml

http://www.postgresql.org/docs/current/interactive/functions-xml.html

Tables are flat, explain output is not.

If there is a relationship between the items then that needs to be
expressed in the XML structure, either by use of child nodes or
attributes. Relying on the sequence of nodes, if that's what you're
doing, is not a good idea, and will make postprocessing the XML using
XSLT, for example, quite a bit harder. (Processing a foo that comes
after a bar is possible but not as natural as processing a foo that is a
child or attribute of a bar)

Anyway, I think what this discussion points out is that we actually need
a formal XML Schema for this output.

cheers

andrew

#40Andres Freund
andres@anarazel.de
In reply to: Andrew Dunstan (#39)
Re: machine-readable explain output

On 06/16/2009 03:45 PM, Andrew Dunstan wrote:

3. We have existing precedent for this design pattern in, e.g.
table_to_xml
http://www.postgresql.org/docs/current/interactive/functions-xml.html

Tables are flat, explain output is not.

Comparing Greg's approach with Robert's it seems to me that Robert's
approach isn't flatter than Greg's - it just relies more on nodes.

If there is a relationship between the items then that needs to be
expressed in the XML structure, either by use of child nodes or
attributes. Relying on the sequence of nodes, if that's what you're
doing, is not a good idea, and will make postprocessing the XML using
XSLT, for example, quite a bit harder. (Processing a foo that comes
after a bar is possible but not as natural as processing a foo that is a
child or attribute of a bar)

How would you model something like:
<plans>
<plan> ... </plan>
<plan> ... </plan>
...
</plans>
otherwise?

There are potentially unlimited number of child nodes - AppendNode for
example can have any number of them. Sure, you can give each <plan> node
a 'offset=' id, but that doesn't buy much.
I don't see how that could be much improved by using child-nodes (or
even worse attributes).

That is as far as I have seen the only place where the format relies on
the sequence of nodes.

Anyway, I think what this discussion points out is that we actually need
a formal XML Schema for this output.

Agreed.

If helpful I can create a schema for the current format.

Andres

#41Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#39)
Re: machine-readable explain output

On Tue, Jun 16, 2009 at 9:45 AM, Andrew Dunstan<andrew@dunslane.net> wrote:

Robert Haas wrote:

3. We have existing precedent for this design pattern in, e.g.
table_to_xml

http://www.postgresql.org/docs/current/interactive/functions-xml.html

Tables are flat, explain output is not.

If there is a relationship between the items then that needs to be expressed
in the XML structure, either by use of child nodes or attributes. Relying on
the sequence of nodes, if that's what you're doing, is not a good idea, and

I'm not doing that. Period, full stop. The discussion was only about
attributes vs. child nodes.

Anyway, I think what this discussion points out is that we actually need a
formal XML Schema for this output.

Well, I don't know how to write one, and am not terribly interested in
learning. Perhaps someone else would be interested?

...Robert

#42Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#40)
Re: machine-readable explain output

On Tue, Jun 16, 2009 at 10:30 AM, Andres Freund<andres@anarazel.de> wrote:

How would you model something like:
<plans>
 <plan> ... </plan>
 <plan> ... </plan>
 ...
</plans>
otherwise?

There are potentially unlimited number of child nodes - AppendNode for
example can have any number of them. Sure, you can give each <plan> node a
'offset=' id, but that doesn't buy much.
I don't see how that could be much improved by using child-nodes (or even
worse attributes).

Note that even in this case we DON'T rely on the ordering of the
nodes. The inner <plan> nodes have child nodes which contain their
relationship to the parent.

...Robert

#43Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#42)
Re: machine-readable explain output

Hi,

On 06/16/2009 04:32 PM, Robert Haas wrote:

On Tue, Jun 16, 2009 at 10:30 AM, Andres Freund<andres@anarazel.de> wrote:

How would you model something like:
<plans>
<plan> ...</plan>
<plan> ...</plan>
...
</plans>
otherwise?

There are potentially unlimited number of child nodes - AppendNode for
example can have any number of them. Sure, you can give each<plan> node a
'offset=' id, but that doesn't buy much.
I don't see how that could be much improved by using child-nodes (or even
worse attributes).

Note that even in this case we DON'T rely on the ordering of the
nodes. The inner<plan> nodes have child nodes which contain their
relationship to the parent.

Not in the case of Append nodes, but I fail to see a problem there, so...

Andres

#44Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Haas (#41)
Re: machine-readable explain output

Robert Haas wrote:

If there is a relationship between the items then that needs to be expressed
in the XML structure, either by use of child nodes or attributes. Relying on
the sequence of nodes, if that's what you're doing, is not a good idea, and

I'm not doing that. Period, full stop. The discussion was only about
attributes vs. child nodes.

OK, I misread something you wrote, which prompted me to say that.
Rereading it I realise my error. My apologies.

cheers

andrew

#45Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#44)
Re: machine-readable explain output

On Tue, Jun 16, 2009 at 10:59 AM, Andrew Dunstan<andrew@dunslane.net> wrote:

Robert Haas wrote:

If there is a relationship between the items then that needs to be
expressed
in the XML structure, either by use of child nodes or attributes. Relying
on
the sequence of nodes, if that's what you're doing, is not a good idea,
and

I'm not doing that.  Period, full stop.  The discussion was only about
attributes vs. child nodes.

OK, I misread something you wrote, which prompted me to say that. Rereading
it I realise my error. My apologies.

No problem, no apologies needed. I guess we do emit nodes like append
plans in the same order that they'd be emitted in text mode. Right
now we don't emit any additional information beyond putting them in
the same order, but I suppose that could be changed if needs be.

...Robert

#46Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#43)
Re: machine-readable explain output

Andres Freund <andres@anarazel.de> writes:

On 06/16/2009 04:32 PM, Robert Haas wrote:

Note that even in this case we DON'T rely on the ordering of the
nodes. The inner<plan> nodes have child nodes which contain their
relationship to the parent.

Not in the case of Append nodes, but I fail to see a problem there, so...

The order of Append child nodes is in fact significant. If this
representation loses that information then it needs to be fixed.
However, is it really so bad to be relying on node order for this?

regards, tom lane

#47Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#46)
Re: machine-readable explain output

Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

On 06/16/2009 04:32 PM, Robert Haas wrote:

Note that even in this case we DON'T rely on the ordering of the
nodes. The inner<plan> nodes have child nodes which contain their
relationship to the parent.

Not in the case of Append nodes, but I fail to see a problem there, so...

The order of Append child nodes is in fact significant. If this
representation loses that information then it needs to be fixed.
However, is it really so bad to be relying on node order for this?

No, if there is a genuine sequence of items then relying on node order
is just fine. My earlier (mistaken) reference was to possibly relying on
node order for a non-sequence relationship.

cheers

andrew

#48Andrew Dunstan
andrew@dunslane.net
In reply to: Andres Freund (#40)
Re: machine-readable explain output

Andres Freund wrote:

Anyway, I think what this discussion points out is that we actually need
a formal XML Schema for this output.

Agreed.

If helpful I can create a schema for the current format.

That will give us a useful starting point.

cheers

andrew

#49Greg Stark
gsstark@mit.edu
In reply to: Andres Freund (#36)
Re: machine-readable explain output

On Tue, Jun 16, 2009 at 1:53 PM, Andres Freund<andres@anarazel.de> wrote:

While that also looks sensible the more structured variant makes it easier
to integrate additional stats which may not easily be pressed in the
'attribute' format. As a fastly contrived example you could have io
statistics over time like:
<iostat>
  <stat time="10" name=pagefault>...</stat>
  <stat time="20" name=pagefault>...</stat>
  <stat time="30" name=pagefault>...</stat>
</iostat>

Something like that would be harder with your variant.

Actually that's exactly the kind of example I had in mind to make easier.

I'm picturing adding a new tag, such as <iostats>, or actually I was
thinking of <dtrace>. If we have separate tags for all the estimates
and actual timings then any tags which come with the <iostat> or
<dtrace> option would just get mixed up with the estimates and timing
info.

Each new module would provide a single tag which would have some
attributes and some child tags depending on how much structure it
needs. In cases where there's no structure, just a fixed list of
scalars like the existing expected and actual stats I don't see any
advantage to making each scalar a tag. (There's not much disadvantage
except I would have said it was completely unreadable for a human
given that you would have pages and pages of output for a significant
size plan.)

So your plan might look like

<scan type=...>
<expected cost=...></>
<actual time=...></>
<iostats>
<samples>
<sample time=nnn value=nnn></>
</samples>
</iostats>
<dtrace script="foo.d">
<probes>
<probe name=foo result=nnn></>
<probe name=bar result=nnn></>
</probes>
</dtrace>

That would make it easy for a tool like pgadmin which doesn't know
what to do with the iostats to ignore the whole chunk, rather than
have to dig through a list of stats some of which come from iostats
and some from dtrace and some from the instrumentation and have to
figure out which tags are things it can use and which are things it
can't.

--
Gregory Stark
http://mit.edu/~gsstark/resume.pdf

#50Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#49)
Re: machine-readable explain output

Greg Stark <gsstark@mit.edu> writes:

I'm picturing adding a new tag, such as <iostats>, or actually I was
thinking of <dtrace>. If we have separate tags for all the estimates
and actual timings then any tags which come with the <iostat> or
<dtrace> option would just get mixed up with the estimates and timing
info.

FWIW, I like Greg's idea of subdividing the available data this way.
I'm no XML guru, so maybe there is a better way to do it --- but a
very large part of the reason for doing this at all is to have an
extensible format, and part of that IMHO is that client programs should
be able to have some rough idea of what things are even when they
don't know it exactly.

But I'd be just as happy with a naming convention, like
<planner:rowcount> versus <actual:rowcount>, etc. I don't know
enough about XML usage to understand the benefits and costs of
different ways of providing that kind of structure.

regards, tom lane

#51Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#50)
Re: machine-readable explain output

Tom Lane wrote:

But I'd be just as happy with a naming convention, like
<planner:rowcount> versus <actual:rowcount>, etc. I don't know
enough about XML usage to understand the benefits and costs of
different ways of providing that kind of structure.

FYI, you probably don't want this. the ':' is not just another character, it separates the namespace designator from the local name. We probably only want one namespace. You can use '-' or '_' or '.' inside names to give them some structure beyond XML semantics.

cheers

andrew

#52Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#50)
Re: machine-readable explain output

On Tue, Jun 16, 2009 at 12:04 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Greg Stark <gsstark@mit.edu> writes:

I'm picturing adding a new tag, such as <iostats>, or actually I was
thinking of <dtrace>. If we have separate tags for all the estimates
and actual timings then any tags which come with the <iostat> or
<dtrace> option would just get mixed up with the estimates and timing
info.

FWIW, I like Greg's idea of subdividing the available data this way.
I'm no XML guru, so maybe there is a better way to do it --- but a
very large part of the reason for doing this at all is to have an
extensible format, and part of that IMHO is that client programs should
be able to have some rough idea of what things are even when they
don't know it exactly.

I like it too, but I'd like to see us come up with a design that
allows it to be used for all of the output formats (text, XML, and
JSON). I think it we should be looking for a way to allow modules to
publish abstract objects like property-value mappings, or lists of
strings, rather than thinking strictly in terms of XML. If we have a
module called foo that emits property bar with value baz and property
bletch with value quux, then in text format we can print:

Module Foo:
Bar: Bletch
Baz: Quux

In XML we can print:

<Modules>
<Module>
<Module-Name>Foo</Module-Name>
<Bar>Bletch</Bar>
<Baz>Quux</Baz>
</Module>
</Modules>

(or any of about 10 reasonable alternatives that are functionally identical)

In JSON we can print

"Modules" : [
{
"Module Name" : "Foo",
"Bar": "Bletch",
"Baz": "Quux"
}
]

(or any of about 2 reasonable alternatives that are functionally identical)

If we start thinking in terms of "provide an API to insert XML into
the XML-format output", we get back to my original complaint: if the
only way of getting additional data is to piece through the XML
output, then we'll quickly reach the point where users need XSLT and
stylesheets to extract the data they care about. I think that's an
annoyance that is easily avoidable.

...Robert

#53Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#52)
Re: machine-readable explain output

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Jun 16, 2009 at 12:04 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

FWIW, I like Greg's idea of subdividing the available data this way.

I like it too, but I'd like to see us come up with a design that
allows it to be used for all of the output formats (text, XML, and
JSON). I think it we should be looking for a way to allow modules to
publish abstract objects like property-value mappings, or lists of
strings, rather than thinking strictly in terms of XML. If we have a
module called foo that emits property bar with value baz and property
bletch with value quux, then ...

This seems to be missing the point I was trying to make, which is that
a design like that actually offers no leverage at all: if you don't know
all about foo to start with, you have no idea what to do with either bar
or bletch. You can *parse* the data, since it's in XML or JSON or
whatever, but you don't know what it is.

The EXPLAIN problem is a fairly constrained universe: there is going to
be a tree of plan nodes, there are going to be some static properties of
each plan node, and there may or may not be various sorts of estimates
and/or measurements attached to each one. What I'm after is that code
examining the output can know "oh, this is a measurement" even if it
hasn't heard of the particular kind of measurement.

As a concrete example of what I'm thinking about, I'd hope that PgAdmin
would be able to display a graphical summary of a plan tree, and then
pop up measurements associated with one of the nodes when you
right-click on that node. To do this, it doesn't necessarily have to
know all about each specific measurement that a particular backend
version might emit; but it needs to be able to tell which things are
measurements.

regards, tom lane

#54Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#53)
Re: machine-readable explain output

On Tue, Jun 16, 2009 at 1:21 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Jun 16, 2009 at 12:04 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

FWIW, I like Greg's idea of subdividing the available data this way.

I like it too, but I'd like to see us come up with a design that
allows it to be used for all of the output formats (text, XML, and
JSON).  I think it we should be looking for a way to allow modules to
publish abstract objects like property-value mappings, or lists of
strings, rather than thinking strictly in terms of XML.  If we have a
module called foo that emits property bar with value baz and property
bletch with value quux, then ...

This seems to be missing the point I was trying to make, which is that
a design like that actually offers no leverage at all: if you don't know
all about foo to start with, you have no idea what to do with either bar
or bletch.  You can *parse* the data, since it's in XML or JSON or
whatever, but you don't know what it is.

The EXPLAIN problem is a fairly constrained universe: there is going to
be a tree of plan nodes, there are going to be some static properties of
each plan node, and there may or may not be various sorts of estimates
and/or measurements attached to each one.  What I'm after is that code
examining the output can know "oh, this is a measurement" even if it
hasn't heard of the particular kind of measurement.

As a concrete example of what I'm thinking about, I'd hope that PgAdmin
would be able to display a graphical summary of a plan tree, and then
pop up measurements associated with one of the nodes when you
right-click on that node.  To do this, it doesn't necessarily have to
know all about each specific measurement that a particular backend
version might emit; but it needs to be able to tell which things are
measurements.

*scratches head*

So you're looking for a way to categorize the data that appear in the
output by type, like any given piece of data is either a measurement,
an estimate, or a part of the plan structure?

It seems to me that with a sufficiently powerful API, add-on modules
could emit arbitrary stuff that might not fall into the categories
that you've mentioned. For example, there was a previous EXPLAIN XML
patch which contained a bunch of code that spit out plans that were
considered but not chosen. And there could easily be other kinds of
less invasive add-ons that would still want to emit properties that
are formatted as text or lists rather than measurements per se.

I think it's kind of hopeless to think that a third-party module is
going to be able to do much better than to display any unexpected
properties whose value is just text and punt any unexpected properties
whose value is a complex object (nested tags in XML-parlance, hash in
JSON).

I have a feeling I'm still missing the point here...

...Robert

#55Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#54)
Re: machine-readable explain output

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Jun 16, 2009 at 1:21 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

As a concrete example of what I'm thinking about, I'd hope that PgAdmin
would be able to display a graphical summary of a plan tree, and then
pop up measurements associated with one of the nodes when you
right-click on that node.

It seems to me that with a sufficiently powerful API, add-on modules
could emit arbitrary stuff that might not fall into the categories
that you've mentioned.

I don't have a problem with inventing new categories when we need to.
What I'm objecting to is using the above to justify flattening the
design completely, so that the only way to know anything about
a particular datum is to know that type of datum specifically.
There is way more structure in EXPLAIN than that, and we should
design it accordingly.

(Note that any information about rejected plans could not usefully be
attached to the plan tree anyway; it'd have to be put in some other
child of the topmost node.)

And there could easily be other kinds of
less invasive add-ons that would still want to emit properties that
are formatted as text or lists rather than measurements per se.

By "measurement" I did not mean to imply "single number". Text strings
or lists could be handled very easily, I think, especially since there
are explicit ways to represent those in XML.

The main point here is that we have a pretty good idea of what
general-purpose client code is likely to want to do with the data, and
in a lot of cases that does not translate to having to know each node
type explicitly, so long as it can be categorized.

regards, tom lane

#56Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#55)
Re: machine-readable explain output

On Tue, Jun 16, 2009 at 2:12 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

The main point here is that we have a pretty good idea of what
general-purpose client code is likely to want to do with the data, and
in a lot of cases that does not translate to having to know each node
type explicitly, so long as it can be categorized.

I agree. I'm just not seeing the need for an *explicit*
categorization contained within the data itself. For one thing, AIUI,
that's the job of things like an XML Schema, which Andres Freund has
already agreed to write, and I would expect that would be of some
value to tool-writers, else why are we creating it? I also think
scalars and lists are recognizable without any particular additional
markup at all, just by introspection of the contents.

Even if we do need some kind of additional markup, I'm reluctant to
try to design it without some feedback from people writing actual
tools about what they find inadequate in the current output. The good
news is that if this patch gets committed fairly quickly after the
release of 8.4, tool authors should have enough time to discover where
any bodies are buried in time to fix them before 8.5. But I'm really
unconvinced that any of this minor formatting stuff is going to rise
to the level of a real problem.

...Robert

#57Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#56)
1 attachment(s)
Re: machine-readable explain output

On 06/16/2009 09:51 PM, Robert Haas wrote:

On Tue, Jun 16, 2009 at 2:12 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

The main point here is that we have a pretty good idea of what
general-purpose client code is likely to want to do with the data,
and in a lot of cases that does not translate to having to know
each node type explicitly, so long as it can be categorized.

I agree. I'm just not seeing the need for an *explicit*
categorization contained within the data itself. For one thing,
AIUI, that's the job of things like an XML Schema, which Andres
Freund has already agreed to write, and I would expect that would be
of some value to tool-writers, else why are we creating it?

It defines how exactly the output has to look - thats not easily
readable out of explain.c - so anything that could be created and
validated with that schema should be acceptable by $tool - even if
explain may not create it.
Just like EBNF or similar for other languages.

It does not help categorizing values in planner/execution/whatever
categories automatedly by some tool though.

I attached a simple relaxng schema - if somebody likes another format
that should be generatable out of that (using trang). It surely could
use some more work, but I think its detailed enough for now.

I also think scalars and lists are recognizable without any
particular additional markup at all, just by introspection of the
contents.

That somewhat defies the usage of a strictly structured format? Perhaps
I am misunderstanding you though.

On another note it may be interesting to emit the current options to
explain in xml/json format - although that depends whether the option
syntax will be accepted.

Writing the schema I noticed something else I did not like about the
current format:

<Triggers>
<Trigger>
<Trigger>Name</Trigger>
or:
<Constraint>ConstraintName</Constraint>
</Trigger>
</Triggers>

The double usage of "<Trigger/>" seems to be somewhat ugly. Renaming it
to <TriggerName>/<ConstraintName> seems to be a good idea - at least
when staying at the current tag oriented style.

Andres

Attachments:

explain-schema.rngtext/plain; name=explain-schema.rngDownload
#58Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#37)
Re: machine-readable explain output

On Tuesday 16 June 2009 16:22:27 Robert Haas wrote:

1. It didn't seem very wise to go with the approach of trying to do
EVERYTHING with attributes. If I did that, then I'd either get really
long lines that were not easily readable, or I'd have to write some
kind of complicated line wrapping code (which didn't seem to make a
lot of sense for a machine-readable format). The current format isn't
the most beautiful thing I've ever seen, but you don't need a parser
to make sense of it, just a bit of patience.

There are obviously a lot of ways to go about defining an XML format, but here
is another one of them:

A plan is a tree of plan nodes. Each node has some information attached to
it, such as row counts and costs.

If you consider an XML document to be a tree of element nodes, then this falls
into place naturally. Each plan is an element, and all the other information
are attributes.

With this, visual explain would be completely trivial.

#59Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#58)
Re: machine-readable explain output

On Wed, Jun 17, 2009 at 10:27 AM, Peter Eisentraut<peter_e@gmx.net> wrote:

On Tuesday 16 June 2009 16:22:27 Robert Haas wrote:

1. It didn't seem very wise to go with the approach of trying to do
EVERYTHING with attributes.  If I did that, then I'd either get really
long lines that were not easily readable, or I'd have to write some
kind of complicated line wrapping code (which didn't seem to make a
lot of sense for a machine-readable format).  The current format isn't
the most beautiful thing I've ever seen, but you don't need a parser
to make sense of it, just a bit of patience.

There are obviously a lot of ways to go about defining an XML format, but here
is another one of them:

A plan is a tree of plan nodes.  Each node has some information attached to
it, such as row counts and costs.

If you consider an XML document to be a tree of element nodes, then this falls
into place naturally.  Each plan is an element, and all the other information
are attributes.

With this, visual explain would be completely trivial.

So what do you do about things like sort keys and target lists, that
the current code outputs as structured lists?

...Robert

#60Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#58)
Re: machine-readable explain output

On 06/17/2009 04:27 PM, Peter Eisentraut wrote:

On Tuesday 16 June 2009 16:22:27 Robert Haas wrote:

1. It didn't seem very wise to go with the approach of trying to do
EVERYTHING with attributes. If I did that, then I'd either get really
long lines that were not easily readable, or I'd have to write some
kind of complicated line wrapping code (which didn't seem to make a
lot of sense for a machine-readable format). The current format isn't
the most beautiful thing I've ever seen, but you don't need a parser
to make sense of it, just a bit of patience.

There are obviously a lot of ways to go about defining an XML format, but here
is another one of them:

A plan is a tree of plan nodes. Each node has some information attached to
it, such as row counts and costs.
If you consider an XML document to be a tree of element nodes, then this falls
into place naturally. Each plan is an element, and all the other information
are attributes.

So, the only change from the current schema would be to do move all
additional information into attributes?

With this, visual explain would be completely trivial.

Only that some attributes may need some more structure than a single
scalar value.

Also that would need extra handling for each attribute to consider if
its a information about planning or execution...

Andres

#61Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#56)
Re: machine-readable explain output

On Tuesday 16 June 2009 22:51:37 Robert Haas wrote:

I agree. I'm just not seeing the need for an *explicit*
categorization contained within the data itself. For one thing, AIUI,
that's the job of things like an XML Schema, which Andres Freund has
already agreed to write, and I would expect that would be of some
value to tool-writers, else why are we creating it?

A schema will just tell which documents are valid, not what they mean. That
is the job of XML ontologies, which are about as pie-in-the-sky as the
semantic web that they are supposed to end up building.

#62Peter Eisentraut
peter_e@gmx.net
In reply to: Tom Lane (#53)
Re: machine-readable explain output

On Tuesday 16 June 2009 20:21:21 Tom Lane wrote:

As a concrete example of what I'm thinking about, I'd hope that PgAdmin
would be able to display a graphical summary of a plan tree, and then
pop up measurements associated with one of the nodes when you
right-click on that node. To do this, it doesn't necessarily have to
know all about each specific measurement that a particular backend
version might emit; but it needs to be able to tell which things are
measurements.

To do this, you pack all "measurements" into a <measurement> element, and then
tools are just told to display those.

Really, this isn't much different (or at all different) from designing an
extensible tree data structure in any programming language.

#63Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#62)
Re: machine-readable explain output

On Wed, Jun 17, 2009 at 10:40 AM, Peter Eisentraut<peter_e@gmx.net> wrote:

On Tuesday 16 June 2009 20:21:21 Tom Lane wrote:

As a concrete example of what I'm thinking about, I'd hope that PgAdmin
would be able to display a graphical summary of a plan tree, and then
pop up measurements associated with one of the nodes when you
right-click on that node.  To do this, it doesn't necessarily have to
know all about each specific measurement that a particular backend
version might emit; but it needs to be able to tell which things are
measurements.

To do this, you pack all "measurements" into a <measurement> element, and then
tools are just told to display those.

I think this is markup for the sake of markup. Right now, if we were
to add 10 additional options, all they'd need to do is call
ExplainPropertyText() and the right stuff would happen. If we go this
route, we'll need to worry about getting each property into the right
subgroup, and argue about whether the assignment of properties to
subgroups is correct or whether we need to rearrange the subgroups (is
"sort method" a "measurement"?). Our chances of us not having to
change this again in the future are a lot better if we just report the
data and let third-party applications worry about categorizing it if
they want to.

Possibly it would make sense to introduce groups for the portions of
the output which are added in response to particular options; for
example, we could have a section called "ANALYZE" that contains the
data that is only present when ANALYZE is used. But this has the same
complicating effect on the code. You'd have to get the
explain_tuplesort() stuff into the same sub-node as the analyze times
and loop counts, for example, which would require non-trivial
restructuring of the existing code for no clear benefit. You'll
quickly get into a situation where you print the same information from
completely different parts of the code depending on whether or not the
output is text format, which is going to make maintaining this a bear.

I think the most common use case for this output format is going to be
to feed it to an XML parser and use xpath against it, or feed it into
a JSON parser and then write things like $plan->{"Actual Rows"} or
plan["Actual Rows"], depending on what language you use to process it
after you parse it. Or you may have people who iterate over sort keys
%$plan and print all the values for which !ref $plan->{$key}.
Unnecessary levels of nesting just make the xpath expressions (or
perl/python/javascript hash/array dereferences) longer. Changing tags
to attributes or visca versa changes which xpath expression you use to
get the data you want, but that's about it.

...Robert

#64Noname
tomas@tuxteam.de
In reply to: Andres Freund (#60)
Re: machine-readable explain output

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On Wed, Jun 17, 2009 at 04:32:51PM +0200, Andres Freund wrote:

On 06/17/2009 04:27 PM, Peter Eisentraut wrote:

On Tuesday 16 June 2009 16:22:27 Robert Haas wrote:

1. It didn't seem very wise to go with the approach of trying to do
EVERYTHING with attributes [...]

There are obviously a lot of ways to go about defining an XML format, but
here
is another one of them:

A plan is a tree of plan nodes. Each node has some information attached
to
it, such as row counts and costs.
If you consider an XML document to be a tree of element nodes, then this
falls
into place naturally. Each plan is an element, and all the other
information
are attributes.

[...]

So, the only change from the current schema would be to do move all
additional information into attributes?

With this, visual explain would be completely trivial.

Only that some attributes may need some more structure than a single scalar
value.

Also that would need extra handling for each attribute to consider if its a
information about planning or execution...

One of the common pitfalls of XML is that designers think first in terms
of the XML representation before being clear on the abstract structure
of what they want to represent. This might have something to do with the
overly baroque language (e.g. having two hierarchy "dimensions": tag
nesting and attributes, etc.).

So when Peter writes about "attributes", I would tend _not_ to read them
as "XML attributes" but rather as abstract attributes. Whether they be
actually represented as XML attributes or not will be a pragmatic
decision (XML attributes have little expressive power: they are just
strings whithout structure, as Andres noted).

The other way 'round the abstract model will end up tainted with all the
trade-offs you had to do to represent your data in XML. Look at so many
data descriptions in XML out there and you'll know what I mean.

That's why I always say: XML is horrid as a DDL. But no one listens ;-)

I'm an outsider, so just take my opinion with a fist of salt: but I'd
tend ton design first the abstract structure, then have a look at the
JSON representation and _then_ take the final decisions on the mapping
to XML.

Regards
- -- tomás
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)

iD8DBQFKOa35Bcgs9XrR2kYRAgwCAJ0S066FfZ6q+l2Lv51/t9/1hUOUBQCfRK0e
OzCEM44nW8tF0g5SPyR+5YY=
=bPwn
-----END PGP SIGNATURE-----

#65Peter Eisentraut
peter_e@gmx.net
In reply to: Noname (#64)
Re: machine-readable explain output

On Thursday 18 June 2009 06:01:13 tomas@tuxteam.de wrote:

One of the common pitfalls of XML is that designers think first in terms
of the XML representation before being clear on the abstract structure
of what they want to represent

The other aspect is that designing a useful XML format is pretty much hopeless
without a clear idea about what processing is expected.

Look at HTML and DocBook. Both of those might have been a result of a
community effort to "design an XML format to publish text", but they clearly
have different processing expectations. And there is a whole infrastructure
around each of them to make that work (specifications, DTDs, documented
processing expectations, CSS, stylesheets, etc.). Also note that neither HTML
nor DocBook are for example designed so that a renderer can do something
useful with elements that it doesn't know about.

The current approach to the XML explain format is an attempt to be quite
literally everything to everyone. The only concrete processing target that
has been vaguely described is some kind of presumably visual display in
pgAdmin. The simple element-per-plan-node approach could handle that just
fine, for example. (The other requirements that have been mentioned are that
it should be similar to the JSON format and it should read nicely, which have
some merit, but aren't really going to help what I'm talking about here.)

Also note that for example DocBook and HTML have versions 1, 2, 3, 4, 5. So I
suggest that we do it that way: design something small for concrete
applications, extend it later, but don't expect applications targeting the old
versions to magically be able to process the new versions with full power.