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