*** 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;
  
  /* ----------------------
