Complete list of changes included in this patch:

- Remove unnecesary parameters nkeys, keycols, and qlabel from
show_sort_keys(), and completely unused parameter scanrelid from
show_scan_qual().
- Include the StringInfo buffer used as a staging area for EXPLAIN
output in the ExplainState object, rather than passing it around
separately.
- Refactor some duplicated logic out of
show_scan_qual()/show_upper_qual() into new function show_qual().
- Refactor various bits of duplicated logic from explain_outNode()
into new functions ExplainSubNodes(), ExplainMemberNodes(), and
ExplainScanTarget().
- For consistency with the naming of other functions in this module
and elsewhere, rename explain_outNode() to ExplainNode().
- Instead of having ExplainNode() indent all of the lines of its
output except the first, and making it the caller's responsibility to
properly indent the first line, make ExplainNode() indent the first
line too.
- Instead of having appendStringInfoSpaces() in ruleutils.c and
separate logic to do the same thing in multiple places in explain.c,
implement a single, efficient version of this logic in stringinfo.c.

I'm planning to submit one or more follow-on patches to implement
support for machine-readable EXPLAIN output, but it seems better to
have this part as a separate patch, since I think all of these changes
pretty much stand on their own, and this way it doesn't confuse the
issue of what is going on in the main patch.  One of the important
parts of this tightening is that it substantially reduces the number
of different places that write directly to the output buffer, which is
good because essentially all of the remaining places will need to be
changed to support XML/JSON output.

...Robert
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 42,47 **** explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
--- 42,48 ----
  
  typedef struct ExplainState
  {
+ 	StringInfo	str;			/* output buffer */
  	/* options */
  	bool		printTList;		/* print plan targetlists */
  	bool		printAnalyze;	/* print actual times */
***************
*** 56,78 **** static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
  				StringInfo buf);
  static double elapsed_time(instr_time *starttime);
! static void explain_outNode(StringInfo str,
! 				Plan *plan, PlanState *planstate,
! 				Plan *outer_plan,
! 				int indent, ExplainState *es);
! static void show_plan_tlist(Plan *plan,
! 							StringInfo str, int indent, ExplainState *es);
  static void show_scan_qual(List *qual, const char *qlabel,
! 			   int scanrelid, Plan *scan_plan, Plan *outer_plan,
! 			   StringInfo str, int indent, ExplainState *es);
  static void show_upper_qual(List *qual, const char *qlabel, Plan *plan,
! 				StringInfo str, int indent, ExplainState *es);
! static void show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
! 			   const char *qlabel,
! 			   StringInfo str, int indent, ExplainState *es);
! static void show_sort_info(SortState *sortstate,
! 			   StringInfo str, int indent, ExplainState *es);
  static const char *explain_get_index_name(Oid indexId);
  
  
  /*
--- 57,79 ----
  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);
  
  
  /*
***************
*** 347,359 **** ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
  	Assert(queryDesc->plannedstmt != NULL);
  
  	memset(&es, 0, sizeof(es));
  	es.printTList = verbose;
  	es.printAnalyze = analyze;
  	es.pstmt = queryDesc->plannedstmt;
  	es.rtable = queryDesc->plannedstmt->rtable;
  
! 	explain_outNode(str,
! 					queryDesc->plannedstmt->planTree, queryDesc->planstate,
  					NULL, 0, &es);
  }
  
--- 348,360 ----
  	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, 0, &es);
  }
  
***************
*** 414,420 **** elapsed_time(instr_time *starttime)
  }
  
  /*
!  * explain_outNode -
   *	  converts a Plan node into ascii string and appends it to 'str'
   *
   * planstate points to the executor state node corresponding to the plan node.
--- 415,421 ----
  }
  
  /*
!  * ExplainNode -
   *	  converts a Plan node into ascii string and appends it to 'str'
   *
   * planstate points to the executor state node corresponding to the plan node.
***************
*** 426,442 **** elapsed_time(instr_time *starttime)
   * deciphering runtime keys of an inner indexscan.
   */
  static void
! explain_outNode(StringInfo str,
! 				Plan *plan, PlanState *planstate,
  				Plan *outer_plan,
  				int indent, ExplainState *es)
  {
  	const char *pname;
! 	int			i;
  
  	if (plan == NULL)
  	{
! 		appendStringInfoChar(str, '\n');
  		return;
  	}
  
--- 427,448 ----
   * deciphering runtime keys of an inner indexscan.
   */
  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, "->  ");
! 	}
  
  	if (plan == NULL)
  	{
! 		appendStringInfoChar(es->str, '\n');
  		return;
  	}
  
***************
*** 656,796 **** explain_outNode(StringInfo str,
  			break;
  	}
  
! 	appendStringInfoString(str, pname);
  	switch (nodeTag(plan))
  	{
  		case T_IndexScan:
  			if (ScanDirectionIsBackward(((IndexScan *) plan)->indexorderdir))
! 				appendStringInfoString(str, " Backward");
! 			appendStringInfo(str, " using %s",
  					  explain_get_index_name(((IndexScan *) plan)->indexid));
  			/* FALL THRU */
  		case T_SeqScan:
  		case T_BitmapHeapScan:
  		case T_TidScan:
- 			if (((Scan *) plan)->scanrelid > 0)
- 			{
- 				RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
- 											  es->rtable);
- 				char	   *relname;
- 
- 				/* Assume it's on a real relation */
- 				Assert(rte->rtekind == RTE_RELATION);
- 
- 				/* We only show the rel name, not schema name */
- 				relname = get_rel_name(rte->relid);
- 
- 				appendStringInfo(str, " on %s",
- 								 quote_identifier(relname));
- 				if (strcmp(rte->eref->aliasname, relname) != 0)
- 					appendStringInfo(str, " %s",
- 									 quote_identifier(rte->eref->aliasname));
- 			}
- 			break;
- 		case T_BitmapIndexScan:
- 			appendStringInfo(str, " on %s",
- 				explain_get_index_name(((BitmapIndexScan *) plan)->indexid));
- 			break;
  		case T_SubqueryScan:
- 			if (((Scan *) plan)->scanrelid > 0)
- 			{
- 				RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
- 											  es->rtable);
- 
- 				appendStringInfo(str, " %s",
- 								 quote_identifier(rte->eref->aliasname));
- 			}
- 			break;
  		case T_FunctionScan:
- 			if (((Scan *) plan)->scanrelid > 0)
- 			{
- 				RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
- 											  es->rtable);
- 				Node	   *funcexpr;
- 				char	   *proname;
- 
- 				/* Assert it's on a RangeFunction */
- 				Assert(rte->rtekind == RTE_FUNCTION);
- 
- 				/*
- 				 * If the expression is still a function call, we can get the
- 				 * real name of the function.  Otherwise, punt (this can
- 				 * happen if the optimizer simplified away the function call,
- 				 * for example).
- 				 */
- 				funcexpr = ((FunctionScan *) plan)->funcexpr;
- 				if (funcexpr && IsA(funcexpr, FuncExpr))
- 				{
- 					Oid			funcid = ((FuncExpr *) funcexpr)->funcid;
- 
- 					/* We only show the func name, not schema name */
- 					proname = get_func_name(funcid);
- 				}
- 				else
- 					proname = rte->eref->aliasname;
- 
- 				appendStringInfo(str, " on %s",
- 								 quote_identifier(proname));
- 				if (strcmp(rte->eref->aliasname, proname) != 0)
- 					appendStringInfo(str, " %s",
- 									 quote_identifier(rte->eref->aliasname));
- 			}
- 			break;
  		case T_ValuesScan:
- 			if (((Scan *) plan)->scanrelid > 0)
- 			{
- 				RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
- 											  es->rtable);
- 				char	   *valsname;
- 
- 				/* Assert it's on a values rte */
- 				Assert(rte->rtekind == RTE_VALUES);
- 
- 				valsname = rte->eref->aliasname;
- 
- 				appendStringInfo(str, " on %s",
- 								 quote_identifier(valsname));
- 			}
- 			break;
  		case T_CteScan:
- 			if (((Scan *) plan)->scanrelid > 0)
- 			{
- 				RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
- 											  es->rtable);
- 
- 				/* Assert it's on a non-self-reference CTE */
- 				Assert(rte->rtekind == RTE_CTE);
- 				Assert(!rte->self_reference);
- 
- 				appendStringInfo(str, " on %s",
- 								 quote_identifier(rte->ctename));
- 				if (strcmp(rte->eref->aliasname, rte->ctename) != 0)
- 					appendStringInfo(str, " %s",
- 									 quote_identifier(rte->eref->aliasname));
- 			}
- 			break;
  		case T_WorkTableScan:
! 			if (((Scan *) plan)->scanrelid > 0)
! 			{
! 				RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
! 											  es->rtable);
! 
! 				/* Assert it's on a self-reference CTE */
! 				Assert(rte->rtekind == RTE_CTE);
! 				Assert(rte->self_reference);
! 
! 				appendStringInfo(str, " on %s",
! 								 quote_identifier(rte->ctename));
! 				if (strcmp(rte->eref->aliasname, rte->ctename) != 0)
! 					appendStringInfo(str, " %s",
! 									 quote_identifier(rte->eref->aliasname));
! 			}
  			break;
  		default:
  			break;
  	}
  
! 	appendStringInfo(str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
  					 plan->startup_cost, plan->total_cost,
  					 plan->plan_rows, plan->plan_width);
  
--- 662,695 ----
  			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:
  		case T_TidScan:
  		case T_SubqueryScan:
  		case T_FunctionScan:
  		case T_ValuesScan:
  		case T_CteScan:
  		case T_WorkTableScan:
! 			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);
  
***************
*** 805,823 **** explain_outNode(StringInfo str,
  	{
  		double		nloops = planstate->instrument->nloops;
  
! 		appendStringInfo(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(str, " (never executed)");
! 	appendStringInfoChar(str, '\n');
  
  	/* target list */
  	if (es->printTList)
! 		show_plan_tlist(plan, str, indent, es);
  
  	/* quals, sort keys, etc */
  	switch (nodeTag(plan))
--- 704,723 ----
  	{
  		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)
! 		show_plan_tlist(plan, indent, es);
  
  	/* quals, sort keys, etc */
  	switch (nodeTag(plan))
***************
*** 825,871 **** explain_outNode(StringInfo str,
  		case T_IndexScan:
  			show_scan_qual(((IndexScan *) plan)->indexqualorig,
  						   "Index Cond",
! 						   ((Scan *) plan)->scanrelid,
! 						   plan, outer_plan,
! 						   str, indent, es);
  			show_scan_qual(plan->qual,
  						   "Filter",
! 						   ((Scan *) plan)->scanrelid,
! 						   plan, outer_plan,
! 						   str, indent, es);
  			break;
  		case T_BitmapIndexScan:
  			show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
  						   "Index Cond",
! 						   ((Scan *) plan)->scanrelid,
! 						   plan, outer_plan,
! 						   str, indent, es);
  			break;
  		case T_BitmapHeapScan:
  			/* XXX do we want to show this in production? */
  			show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
  						   "Recheck Cond",
! 						   ((Scan *) plan)->scanrelid,
! 						   plan, outer_plan,
! 						   str, indent, es);
  			/* FALL THRU */
  		case T_SeqScan:
  		case T_FunctionScan:
  		case T_ValuesScan:
  		case T_CteScan:
  		case T_WorkTableScan:
- 			show_scan_qual(plan->qual,
- 						   "Filter",
- 						   ((Scan *) plan)->scanrelid,
- 						   plan, outer_plan,
- 						   str, indent, es);
- 			break;
  		case T_SubqueryScan:
  			show_scan_qual(plan->qual,
  						   "Filter",
! 						   ((Scan *) plan)->scanrelid,
! 						   plan, outer_plan,
! 						   str, indent, es);
  			break;
  		case T_TidScan:
  			{
--- 725,755 ----
  		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:
  		case T_ValuesScan:
  		case T_CteScan:
  		case T_WorkTableScan:
  		case T_SubqueryScan:
  			show_scan_qual(plan->qual,
  						   "Filter",
! 						   plan, outer_plan, indent, es);
  			break;
  		case T_TidScan:
  			{
***************
*** 879,946 **** explain_outNode(StringInfo str,
  					tidquals = list_make1(make_orclause(tidquals));
  				show_scan_qual(tidquals,
  							   "TID Cond",
! 							   ((Scan *) plan)->scanrelid,
! 							   plan, outer_plan,
! 							   str, indent, es);
  				show_scan_qual(plan->qual,
  							   "Filter",
! 							   ((Scan *) plan)->scanrelid,
! 							   plan, outer_plan,
! 							   str, indent, es);
  			}
  			break;
  		case T_NestLoop:
  			show_upper_qual(((NestLoop *) plan)->join.joinqual,
! 							"Join Filter", plan,
! 							str, indent, es);
  			show_upper_qual(plan->qual,
! 							"Filter", plan,
! 							str, indent, es);
  			break;
  		case T_MergeJoin:
  			show_upper_qual(((MergeJoin *) plan)->mergeclauses,
! 							"Merge Cond", plan,
! 							str, indent, es);
  			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
! 							"Join Filter", plan,
! 							str, indent, es);
  			show_upper_qual(plan->qual,
! 							"Filter", plan,
! 							str, indent, es);
  			break;
  		case T_HashJoin:
  			show_upper_qual(((HashJoin *) plan)->hashclauses,
! 							"Hash Cond", plan,
! 							str, indent, es);
  			show_upper_qual(((HashJoin *) plan)->join.joinqual,
! 							"Join Filter", plan,
! 							str, indent, es);
  			show_upper_qual(plan->qual,
! 							"Filter", plan,
! 							str, indent, es);
  			break;
  		case T_Agg:
  		case T_Group:
  			show_upper_qual(plan->qual,
! 							"Filter", plan,
! 							str, indent, es);
  			break;
  		case T_Sort:
! 			show_sort_keys(plan,
! 						   ((Sort *) plan)->numCols,
! 						   ((Sort *) plan)->sortColIdx,
! 						   "Sort Key",
! 						   str, indent, es);
! 			show_sort_info((SortState *) planstate,
! 						   str, indent, es);
  			break;
  		case T_Result:
  			show_upper_qual((List *) ((Result *) plan)->resconstantqual,
! 							"One-Time Filter", plan,
! 							str, indent, es);
  			show_upper_qual(plan->qual,
! 							"Filter", plan,
! 							str, indent, es);
  			break;
  		default:
  			break;
--- 763,810 ----
  					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;
***************
*** 948,988 **** explain_outNode(StringInfo str,
  
  	/* initPlan-s */
  	if (plan->initPlan)
! 	{
! 		ListCell   *lst;
! 
! 		foreach(lst, planstate->initPlan)
! 		{
! 			SubPlanState *sps = (SubPlanState *) lfirst(lst);
! 			SubPlan    *sp = (SubPlan *) sps->xprstate.expr;
! 
! 			for (i = 0; i < indent; i++)
! 				appendStringInfo(str, "  ");
! 			appendStringInfo(str, "  %s\n", sp->plan_name);
! 			for (i = 0; i < indent; i++)
! 				appendStringInfo(str, "  ");
! 			appendStringInfo(str, "    ->  ");
! 			explain_outNode(str,
! 							exec_subplan_get_plan(es->pstmt, sp),
! 							sps->planstate,
! 							NULL,
! 							indent + 4, es);
! 		}
! 	}
  
  	/* lefttree */
  	if (outerPlan(plan))
  	{
- 		for (i = 0; i < indent; i++)
- 			appendStringInfo(str, "  ");
- 		appendStringInfo(str, "  ->  ");
- 
  		/*
  		 * Ordinarily we don't pass down our own outer_plan value to our child
  		 * nodes, but in bitmap scan trees we must, since the bottom
  		 * BitmapIndexScan nodes may have outer references.
  		 */
! 		explain_outNode(str, outerPlan(plan),
  						outerPlanState(planstate),
  						IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
  						indent + 3, es);
--- 812,828 ----
  
  	/* initPlan-s */
  	if (plan->initPlan)
! 		ExplainSubNodes(planstate->initPlan, indent, es);
  
  	/* lefttree */
  	if (outerPlan(plan))
  	{
  		/*
  		 * Ordinarily we don't pass down our own outer_plan value to our child
  		 * nodes, but in bitmap scan trees we must, since the bottom
  		 * BitmapIndexScan nodes may have outer references.
  		 */
! 		ExplainNode(outerPlan(plan),
  						outerPlanState(planstate),
  						IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
  						indent + 3, es);
***************
*** 991,1081 **** explain_outNode(StringInfo str,
  	/* righttree */
  	if (innerPlan(plan))
  	{
! 		for (i = 0; i < indent; i++)
! 			appendStringInfo(str, "  ");
! 		appendStringInfo(str, "  ->  ");
! 		explain_outNode(str, innerPlan(plan),
  						innerPlanState(planstate),
  						outerPlan(plan),
  						indent + 3, es);
  	}
  
! 	if (IsA(plan, Append))
! 	{
! 		Append	   *appendplan = (Append *) plan;
! 		AppendState *appendstate = (AppendState *) planstate;
! 		ListCell   *lst;
! 		int			j;
! 
! 		j = 0;
! 		foreach(lst, appendplan->appendplans)
! 		{
! 			Plan	   *subnode = (Plan *) lfirst(lst);
! 
! 			for (i = 0; i < indent; i++)
! 				appendStringInfo(str, "  ");
! 			appendStringInfo(str, "  ->  ");
! 
! 			/*
! 			 * Ordinarily we don't pass down our own outer_plan value to our
! 			 * child nodes, but in an Append we must, since we might be
! 			 * looking at an appendrel indexscan with outer references from
! 			 * the member scans.
! 			 */
! 			explain_outNode(str, subnode,
! 							appendstate->appendplans[j],
! 							outer_plan,
! 							indent + 3, es);
! 			j++;
! 		}
! 	}
! 
! 	if (IsA(plan, BitmapAnd))
! 	{
! 		BitmapAnd  *bitmapandplan = (BitmapAnd *) plan;
! 		BitmapAndState *bitmapandstate = (BitmapAndState *) planstate;
! 		ListCell   *lst;
! 		int			j;
! 
! 		j = 0;
! 		foreach(lst, bitmapandplan->bitmapplans)
! 		{
! 			Plan	   *subnode = (Plan *) lfirst(lst);
! 
! 			for (i = 0; i < indent; i++)
! 				appendStringInfo(str, "  ");
! 			appendStringInfo(str, "  ->  ");
! 
! 			explain_outNode(str, subnode,
! 							bitmapandstate->bitmapplans[j],
! 							outer_plan, /* pass down same outer plan */
! 							indent + 3, es);
! 			j++;
! 		}
! 	}
! 
! 	if (IsA(plan, BitmapOr))
! 	{
! 		BitmapOr   *bitmaporplan = (BitmapOr *) plan;
! 		BitmapOrState *bitmaporstate = (BitmapOrState *) planstate;
! 		ListCell   *lst;
! 		int			j;
! 
! 		j = 0;
! 		foreach(lst, bitmaporplan->bitmapplans)
! 		{
! 			Plan	   *subnode = (Plan *) lfirst(lst);
! 
! 			for (i = 0; i < indent; i++)
! 				appendStringInfo(str, "  ");
! 			appendStringInfo(str, "  ->  ");
! 
! 			explain_outNode(str, subnode,
! 							bitmaporstate->bitmapplans[j],
! 							outer_plan, /* pass down same outer plan */
! 							indent + 3, es);
! 			j++;
! 		}
  	}
  
  	if (IsA(plan, SubqueryScan))
--- 831,860 ----
  	/* 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;
  	}
  
  	if (IsA(plan, SubqueryScan))
***************
*** 1084,1094 **** explain_outNode(StringInfo str,
  		SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
  		Plan	   *subnode = subqueryscan->subplan;
  
! 		for (i = 0; i < indent; i++)
! 			appendStringInfo(str, "  ");
! 		appendStringInfo(str, "  ->  ");
! 
! 		explain_outNode(str, subnode,
  						subquerystate->subplan,
  						NULL,
  						indent + 3, es);
--- 863,869 ----
  		SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
  		Plan	   *subnode = subqueryscan->subplan;
  
! 		ExplainNode(subnode,
  						subquerystate->subplan,
  						NULL,
  						indent + 3, es);
***************
*** 1096,1130 **** explain_outNode(StringInfo str,
  
  	/* subPlan-s */
  	if (planstate->subPlan)
! 	{
! 		ListCell   *lst;
! 
! 		foreach(lst, planstate->subPlan)
! 		{
! 			SubPlanState *sps = (SubPlanState *) lfirst(lst);
! 			SubPlan    *sp = (SubPlan *) sps->xprstate.expr;
! 
! 			for (i = 0; i < indent; i++)
! 				appendStringInfo(str, "  ");
! 			appendStringInfo(str, "  %s\n", sp->plan_name);
! 			for (i = 0; i < indent; i++)
! 				appendStringInfo(str, "  ");
! 			appendStringInfo(str, "    ->  ");
! 			explain_outNode(str,
! 							exec_subplan_get_plan(es->pstmt, sp),
! 							sps->planstate,
! 							NULL,
! 							indent + 4, es);
! 		}
! 	}
  }
  
  /*
   * Show the targetlist of a plan node
   */
  static void
! show_plan_tlist(Plan *plan,
! 				StringInfo str, int indent, ExplainState *es)
  {
  	List	   *context;
  	bool		useprefix;
--- 871,884 ----
  
  	/* 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;
***************
*** 1149,1157 **** show_plan_tlist(Plan *plan,
  	useprefix = list_length(es->rtable) > 1;
  
  	/* Emit line prefix */
! 	for (i = 0; i < indent; i++)
! 		appendStringInfo(str, "  ");
! 	appendStringInfo(str, "  Output: ");
  
  	/* Deparse each non-junk result column */
  	i = 0;
--- 903,910 ----
  	useprefix = list_length(es->rtable) > 1;
  
  	/* Emit line prefix */
! 	appendStringInfoSpaces(es->str, indent * 2);
! 	appendStringInfo(es->str, "  Output: ");
  
  	/* Deparse each non-junk result column */
  	i = 0;
***************
*** 1162,1192 **** show_plan_tlist(Plan *plan,
  		if (tle->resjunk)
  			continue;
  		if (i++ > 0)
! 			appendStringInfo(str, ", ");
! 		appendStringInfoString(str,
  							   deparse_expression((Node *) tle->expr, context,
  												  useprefix, false));
  	}
  
! 	appendStringInfoChar(str, '\n');
  }
  
  /*
!  * Show a qualifier expression for a scan plan node
   *
   * Note: outer_plan is the referent for any OUTER vars in the scan qual;
   * this would be the outer side of a nestloop plan.  Pass NULL if none.
   */
  static void
! show_scan_qual(List *qual, const char *qlabel,
! 			   int scanrelid, Plan *scan_plan, Plan *outer_plan,
! 			   StringInfo str, int indent, ExplainState *es)
  {
  	List	   *context;
- 	bool		useprefix;
  	Node	   *node;
  	char	   *exprstr;
- 	int			i;
  
  	/* No work if empty qual */
  	if (qual == NIL)
--- 915,943 ----
  		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');
  }
  
  /*
!  * Show a qualifier expression
   *
   * Note: outer_plan is the referent for any OUTER vars in the scan qual;
   * this would be the outer side of a nestloop plan.  Pass NULL if none.
   */
  static void
! show_qual(List *qual, const char *qlabel,
! 			   Plan *plan, Plan *outer_plan,
! 			   int indent, bool useprefix, ExplainState *es)
  {
  	List	   *context;
  	Node	   *node;
  	char	   *exprstr;
  
  	/* No work if empty qual */
  	if (qual == NIL)
***************
*** 1196,1214 **** show_scan_qual(List *qual, const char *qlabel,
  	node = (Node *) make_ands_explicit(qual);
  
  	/* Set up deparsing context */
! 	context = deparse_context_for_plan((Node *) scan_plan,
  									   (Node *) outer_plan,
  									   es->rtable,
  									   es->pstmt->subplans);
- 	useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan));
  
  	/* Deparse the expression */
  	exprstr = deparse_expression(node, context, useprefix, false);
  
  	/* And add to str */
! 	for (i = 0; i < indent; i++)
! 		appendStringInfo(str, "  ");
! 	appendStringInfo(str, "  %s: %s\n", qlabel, exprstr);
  }
  
  /*
--- 947,976 ----
  	node = (Node *) make_ands_explicit(qual);
  
  	/* Set up deparsing context */
! 	context = deparse_context_for_plan((Node *) plan,
  									   (Node *) outer_plan,
  									   es->rtable,
  									   es->pstmt->subplans);
  
  	/* Deparse the expression */
  	exprstr = deparse_expression(node, context, useprefix, false);
  
  	/* And add to str */
! 	appendStringInfoSpaces(es->str, indent * 2);
! 	appendStringInfo(es->str, "  %s: %s\n", qlabel, exprstr);
! }
! 
! /*
!  * Show a qualifier expression for a scan plan node
!  */
! 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);
  }
  
  /*
***************
*** 1216,1270 **** show_scan_qual(List *qual, const char *qlabel,
   */
  static void
  show_upper_qual(List *qual, const char *qlabel, Plan *plan,
! 				StringInfo str, int indent, ExplainState *es)
  {
! 	List	   *context;
! 	bool		useprefix;
! 	Node	   *node;
! 	char	   *exprstr;
! 	int			i;
! 
! 	/* No work if empty qual */
! 	if (qual == NIL)
! 		return;
! 
! 	/* Set up deparsing context */
! 	context = deparse_context_for_plan((Node *) plan,
! 									   NULL,
! 									   es->rtable,
! 									   es->pstmt->subplans);
! 	useprefix = list_length(es->rtable) > 1;
! 
! 	/* Deparse the expression */
! 	node = (Node *) make_ands_explicit(qual);
! 	exprstr = deparse_expression(node, context, useprefix, false);
  
! 	/* And add to str */
! 	for (i = 0; i < indent; i++)
! 		appendStringInfo(str, "  ");
! 	appendStringInfo(str, "  %s: %s\n", qlabel, exprstr);
  }
  
  /*
   * Show the sort keys for a Sort node.
   */
  static void
! show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
! 			   const char *qlabel,
! 			   StringInfo str, int indent, ExplainState *es)
  {
  	List	   *context;
  	bool		useprefix;
  	int			keyno;
  	char	   *exprstr;
! 	int			i;
  
  	if (nkeys <= 0)
  		return;
  
! 	for (i = 0; i < indent; i++)
! 		appendStringInfo(str, "  ");
! 	appendStringInfo(str, "  %s: ", qlabel);
  
  	/* Set up deparsing context */
  	context = deparse_context_for_plan((Node *) sortplan,
--- 978,1008 ----
   */
  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;
  	int			keyno;
  	char	   *exprstr;
! 	int			nkeys = ((Sort *) sortplan)->numCols;
! 	AttrNumber *keycols = ((Sort *) sortplan)->sortColIdx;
  
  	if (nkeys <= 0)
  		return;
  
! 	appendStringInfoSpaces(es->str, indent * 2);
! 	appendStringInfoString(es->str, "  Sort Key: ");
  
  	/* Set up deparsing context */
  	context = deparse_context_for_plan((Node *) sortplan,
***************
*** 1286,1316 **** show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
  									 useprefix, true);
  		/* And add to str */
  		if (keyno > 0)
! 			appendStringInfo(str, ", ");
! 		appendStringInfoString(str, exprstr);
  	}
  
! 	appendStringInfo(str, "\n");
  }
  
  /*
   * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node
   */
  static void
! show_sort_info(SortState *sortstate,
! 			   StringInfo str, int indent, ExplainState *es)
  {
  	Assert(IsA(sortstate, SortState));
  	if (es->printAnalyze && sortstate->sort_Done &&
  		sortstate->tuplesortstate != NULL)
  	{
  		char	   *sortinfo;
- 		int			i;
  
  		sortinfo = tuplesort_explain((Tuplesortstate *) sortstate->tuplesortstate);
! 		for (i = 0; i < indent; i++)
! 			appendStringInfo(str, "  ");
! 		appendStringInfo(str, "  %s\n", sortinfo);
  		pfree(sortinfo);
  	}
  }
--- 1024,1051 ----
  									 useprefix, true);
  		/* And add to str */
  		if (keyno > 0)
! 			appendStringInfo(es->str, ", ");
! 		appendStringInfoString(es->str, exprstr);
  	}
  
! 	appendStringInfo(es->str, "\n");
  }
  
  /*
   * 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);
  	}
  }
***************
*** 1340,1342 **** explain_get_index_name(Oid indexId)
--- 1075,1186 ----
  	}
  	return result;
  }
+ 
+ /*
+  * Explain details for Scan nodes.
+  */
+ static void
+ ExplainScanTarget(Scan *plan, ExplainState *es)
+ {
+ 	char *objectname = NULL;
+ 	Node *funcexpr;
+ 	RangeTblEntry *rte;
+ 
+ 	if (plan->scanrelid <= 0)
+ 		return;
+ 	rte = rt_fetch(plan->scanrelid, es->rtable);
+ 
+ 	switch (nodeTag(plan))
+ 	{
+ 		case T_IndexScan:
+ 		case T_SeqScan:
+ 		case T_BitmapHeapScan:
+ 		case T_TidScan:
+ 			/* Assert it's on a real relation */
+ 			Assert(rte->rtekind == RTE_RELATION);
+ 			objectname = get_rel_name(rte->relid);
+ 			break;
+ 		case T_FunctionScan:
+ 			/* Assert it's on a RangeFunction */
+ 			Assert(rte->rtekind == RTE_FUNCTION);
+ 
+ 			/*
+ 			 * If the expression is still a function call, we can get the
+ 			 * real name of the function.  Otherwise, punt (this can
+ 			 * happen if the optimizer simplified away the function call,
+ 			 * for example).
+ 			 */
+ 			funcexpr = ((FunctionScan *) plan)->funcexpr;
+ 			if (funcexpr && IsA(funcexpr, FuncExpr))
+ 			{
+ 				Oid			funcid = ((FuncExpr *) funcexpr)->funcid;
+ 				objectname = get_func_name(funcid);
+ 			}
+ 			break;
+ 		case T_ValuesScan:
+ 			Assert(rte->rtekind == RTE_VALUES);
+ 			break;
+ 		case T_CteScan:
+ 			/* Assert it's on a non-self-reference CTE */
+ 			Assert(rte->rtekind == RTE_CTE);
+ 			Assert(!rte->self_reference);
+ 			objectname = rte->ctename;
+ 			break;
+ 		case T_WorkTableScan:
+ 			/* Assert it's on a self-reference CTE */
+ 			Assert(rte->rtekind == RTE_CTE);
+ 			Assert(rte->self_reference);
+ 			objectname = rte->ctename;
+ 			break;
+ 		default:
+ 			break;
+ 	}
+ 
+ 	appendStringInfoString(es->str, " on");
+ 	if (objectname != NULL)
+ 		appendStringInfo(es->str, " %s", quote_identifier(objectname));
+ 	if (objectname == NULL || strcmp(rte->eref->aliasname, objectname) != 0)
+ 		appendStringInfo(es->str, " %s",
+ 						 quote_identifier(rte->eref->aliasname));
+ }
+ 
+ /*
+  * Explain details for Append, BitmapAnd, or BitmapOr constutent plans.
+  * Ordinarily we don't pass down outer_plan value to our child nodes, but in
+  * an Append, BitmapAnd, or BitmapOr we must, since these nodes can have outer
+  * references from the member scans.
+  */
+ 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;
+ 	}
+ }
+ 
+ /*
+  * Explain a list of Subplans (or initPlans, which use SubPlan nodes).
+  */
+ static void
+ ExplainSubNodes(List *plans, int indent, ExplainState *es)
+ {
+ 	ListCell   *lst;
+ 
+ 	foreach(lst, plans)
+ 	{
+ 		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);
+ 	}
+ }
*** a/src/backend/lib/stringinfo.c
--- b/src/backend/lib/stringinfo.c
***************
*** 187,192 **** appendStringInfoChar(StringInfo str, char ch)
--- 187,209 ----
  }
  
  /*
+  * appendStringInfoSpaces
+  *
+  * Append spaces to a buffer.
+  */
+ void
+ appendStringInfoSpaces(StringInfo str, int count)
+ {
+ 	/* Make more room if needed */
+ 	enlargeStringInfo(str, count);
+ 
+ 	/* OK, append the spaces */
+ 	while (--count >= 0)
+ 		str->data[str->len++] = ' ';
+ 	str->data[str->len] = '\0';
+ }
+ 
+ /*
   * appendBinaryStringInfo
   *
   * Append arbitrary binary data to a StringInfo, allocating more space
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 187,193 **** static RangeTblEntry *find_rte_by_refname(const char *refname,
  					deparse_context *context);
  static const char *get_simple_binary_op_name(OpExpr *expr);
  static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags);
- static void appendStringInfoSpaces(StringInfo buf, int count);
  static void appendContextKeyword(deparse_context *context, const char *str,
  					 int indentBefore, int indentAfter, int indentPlus);
  static void get_rule_expr(Node *node, deparse_context *context,
--- 187,192 ----
***************
*** 4173,4188 **** isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
  
  
  /*
-  * appendStringInfoSpaces - append spaces to buffer
-  */
- static void
- appendStringInfoSpaces(StringInfo buf, int count)
- {
- 	while (count-- > 0)
- 		appendStringInfoChar(buf, ' ');
- }
- 
- /*
   * appendContextKeyword - append a keyword to buffer
   *
   * If prettyPrint is enabled, perform a line break, and adjust indentation.
--- 4172,4177 ----
*** a/src/include/lib/stringinfo.h
--- b/src/include/lib/stringinfo.h
***************
*** 132,137 **** extern void appendStringInfoChar(StringInfo str, char ch);
--- 132,143 ----
  	 (void)((str)->data[(str)->len] = (ch), (str)->data[++(str)->len] = '\0'))
  
  /*------------------------
+  * appendStringInfoSpaces
+  * Append a given number of spaces to str.
+  */
+ extern void appendStringInfoSpaces(StringInfo str, int count);
+ 
+ /*------------------------
   * appendBinaryStringInfo
   * Append arbitrary binary data to a StringInfo, allocating more space
   * if necessary.
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to