Hi Robert, Hi All,

Patch applies with some offset changes, code changes look sensible, I 
personally like the new syntax and the features it may allow in future. One, 
possibly big, gripe remains though:
The formerly valid statement which cannot be written without the parentheses 
and stay semantically equivalent:
EXPLAIN (SELECT 1 ORDER BY 1) UNION ALL (SELECT 2 ORDER BY 1);
is now not valid anymore (The added %prec UMINUS causes the first '(' to be 
recognize as start of the option list as intended).
This currently can only be resolved by using an option list like:
EXPLAIN (VERBOSE OFF) ...
Its also currently impossible to use an empty set of parentheses to resolve 
this - this could easily be changed though.

I have to admit I don't see a nice solution here except living with the 
incompatibility... Perhaps somebody has a better idea?

Andres

PS: The 'offset corrected' version I tested with is attached
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index b7c0ef9..956af94 100644
*** a/contrib/auto_explain/auto_explain.c
--- b/contrib/auto_explain/auto_explain.c
***************
*** 14,19 ****
--- 14,20 ----
  
  #include "commands/explain.h"
  #include "executor/instrument.h"
+ #include "nodes/makefuncs.h"
  #include "utils/guc.h"
  
  PG_MODULE_MAGIC;
*************** explain_ExecutorEnd(QueryDesc *queryDesc
*** 196,207 ****
  		msec = queryDesc->totaltime->total * 1000.0;
  		if (msec >= auto_explain_log_min_duration)
  		{
  			StringInfoData buf;
  
  			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')
--- 197,210 ----
  		msec = queryDesc->totaltime->total * 1000.0;
  		if (msec >= auto_explain_log_min_duration)
  		{
+ 			ExplainStmt   *stmt = makeExplain(NIL, NULL);
  			StringInfoData buf;
  
  			initStringInfo(&buf);
! 			stmt->analyze =
! 				(queryDesc->doInstrument && auto_explain_log_analyze);
! 			stmt->verbose = auto_explain_log_verbose;
! 			ExplainPrintPlan(&buf, queryDesc, stmt);
  
  			/* Remove last line break */
  			if (buf.len > 0 && buf.data[buf.len - 1] == '\n')
diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index ea9b3a6..afcab08 100644
*** a/doc/src/sgml/ref/explain.sgml
--- b/doc/src/sgml/ref/explain.sgml
*************** PostgreSQL documentation
*** 31,36 ****
--- 31,37 ----
  
   <refsynopsisdiv>
  <synopsis>
+ EXPLAIN [ ( [ { ANALYZE | VERBOSE | COSTS } [ <replaceable class="parameter">boolean_value</replaceable> ] ] [, ...] ) ] <replaceable class="parameter">statement</replaceable>
  EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replaceable>
  </synopsis>
   </refsynopsisdiv>
*************** EXPLAIN [ ANALYZE ] [ VERBOSE ] <replace
*** 70,75 ****
--- 71,86 ----
     are close to reality.
    </para>
  
+   <para>
+    Only the <literal>ANALYZE</literal> and <literal>VERBOSE</literal> options
+    can be specified, and only in the order, without surrounding the option list
+    in parentheses.  Prior to <productname>PostgreSQL</productname> 8.5, the
+    unparenthesized syntax was the only one supported.  It is expected that
+    all new options will be supported only when using the parenthesized syntax,
+    which also allows a value to be specified for each option
+    (e.g. <literal>TRUE</literal> or <literal>FALSE</literal>).
+   </para>
+ 
    <important>
     <para>
      Keep in mind that the statement is actually executed when
*************** ROLLBACK;
*** 99,105 ****
      <term><literal>ANALYZE</literal></term>
      <listitem>
       <para>
!       Carry out the command and show the actual run times.
       </para>
      </listitem>
     </varlistentry>
--- 110,117 ----
      <term><literal>ANALYZE</literal></term>
      <listitem>
       <para>
!       Carry out the command and show the actual run times.  This
!       parameter defaults to <command>OFF</command>.
       </para>
      </listitem>
     </varlistentry>
*************** ROLLBACK;
*** 108,114 ****
      <term><literal>VERBOSE</literal></term>
      <listitem>
       <para>
!       Include the output column list for each node in the plan tree.
       </para>
      </listitem>
     </varlistentry>
--- 120,152 ----
      <term><literal>VERBOSE</literal></term>
      <listitem>
       <para>
!       Include the output column list for each node in the plan tree.  This
!       parameter defaults to <command>OFF</command>.
!      </para>
!     </listitem>
!    </varlistentry>
! 
!    <varlistentry>
!     <term><literal>COSTS</literal></term>
!     <listitem>
!      <para>
!       Include information on the estimated startup and total cost of each
!       plan node, as well as the estimated number of rows and the estimated
!       width of each row.  This parameter defaults to <command>ON</command>.
!      </para>
!     </listitem>
!    </varlistentry>
! 
!    <varlistentry>
!     <term><replaceable class="parameter" />boolean_value</replaceable></term>
!     <listitem>
!      <para>
!       Specifies whether the named parameter should be turned on or off.  You
!       can use the values <literal>TRUE</literal>, <literal>ON</literal>,
!       <literal>YES</literal>, or <literal>1</literal> to request the stated
!       option, and <literal>FALSE</literal>, <literal>OFF</literal>,
!       <literal>NO</literal>, or <literal>0</literal>.  If the Boolean value
!       is omitted, it defaults to <literal>TRUE</literal>.
       </para>
      </listitem>
     </varlistentry>
*************** EXPLAIN SELECT * FROM foo WHERE i = 4;
*** 202,207 ****
--- 240,259 ----
    </para>
  
    <para>
+    Here is the same plan with costs suppressed:
+ 
+ <programlisting>
+ EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE i = 4;
+ 
+         QUERY PLAN
+ ----------------------------
+  Index Scan using fi on foo
+    Index Cond: (i = 4)
+ (2 rows)
+ </programlisting>
+   </para>
+ 
+   <para>
     Here is an example of a query plan for a query using an aggregate
     function:
  
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 950a2f1..efe436f 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** explain_get_index_name_hook_type explain
*** 42,50 ****
--- 42,52 ----
  
  typedef struct ExplainState
  {
+ 	StringInfo	str;			/* output buffer */
  	/* options */
  	bool		printTList;		/* print plan targetlists */
  	bool		printAnalyze;	/* print actual times */
+ 	bool		printCosts;		/* print costs */
  	/* other states */
  	PlannedStmt *pstmt;			/* top of plan */
  	List	   *rtable;			/* range table */
*************** static void ExplainOneQuery(Query *query
*** 56,78 ****
  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);
  
  
  /*
--- 58,80 ----
  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);
  
  
  /*
*************** ExplainOnePlan(PlannedStmt *plannedstmt,
*** 267,273 ****
  
  	/* 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
--- 269,275 ----
  
  	/* Create textual dump of plan tree */
  	initStringInfo(&buf);
! 	ExplainPrintPlan(&buf, queryDesc, stmt);
  
  	/*
  	 * If we ran the command, run any AFTER triggers it queued.  (Note this
*************** ExplainOnePlan(PlannedStmt *plannedstmt,
*** 339,360 ****
   * NB: will not work on utility statements
   */
  void
! ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
! 				 bool analyze, bool verbose)
  {
  	ExplainState es;
  
  	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);
  }
  
  /*
--- 341,362 ----
   * NB: will not work on utility statements
   */
  void
! ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc, ExplainStmt *stmt)
  {
  	ExplainState es;
  
  	Assert(queryDesc->plannedstmt != NULL);
  
  	memset(&es, 0, sizeof(es));
! 	es.str = str;
! 	es.printTList = stmt->verbose;
! 	es.printAnalyze = stmt->analyze;
! 	es.printCosts = stmt->costs;
  	es.pstmt = queryDesc->plannedstmt;
  	es.rtable = queryDesc->plannedstmt->rtable;
  
! 	ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate,
! 				NULL, 0, &es);
  }
  
  /*
*************** elapsed_time(instr_time *starttime)
*** 414,420 ****
  }
  
  /*
!  * 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.
--- 416,422 ----
  }
  
  /*
!  * 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.
*************** elapsed_time(instr_time *starttime)
*** 426,442 ****
   * 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;
  	}
  
--- 428,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;
  	}
  
*************** explain_outNode(StringInfo str,
*** 656,798 ****
  			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);
  
  	/*
  	 * We have to forcibly clean up the instrumentation state because we
--- 662,698 ----
  			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;
  	}
  
! 	if (es->printCosts)
! 		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
*************** explain_outNode(StringInfo str,
*** 805,871 ****
  	{
  		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))
  	{
  		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:
  			{
--- 705,751 ----
  	{
  		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))
  	{
  		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:
  			{
*************** explain_outNode(StringInfo str,
*** 878,946 ****
  				if (list_length(tidquals) > 1)
  					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;
--- 758,799 ----
  				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;
*************** explain_outNode(StringInfo str,
*** 948,1081 ****
  
  	/* 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);
  	}
  
  	/* 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))
--- 801,846 ----
  
  	/* 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);
  	}
  
  	/* 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))
*************** explain_outNode(StringInfo str,
*** 1084,1130 ****
  		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);
  	}
  
  	/* 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;
--- 849,867 ----
  		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;
*************** show_plan_tlist(Plan *plan,
*** 1149,1157 ****
  	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;
--- 886,893 ----
  	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;
*************** show_plan_tlist(Plan *plan,
*** 1162,1192 ****
  		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)
--- 898,925 ----
  		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)
*************** show_scan_qual(List *qual, const char *q
*** 1196,1214 ****
  	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);
  }
  
  /*
--- 929,958 ----
  	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);
  }
  
  /*
*************** show_scan_qual(List *qual, const char *q
*** 1216,1270 ****
   */
  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,
--- 960,990 ----
   */
  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,
*************** show_sort_keys(Plan *sortplan, int nkeys
*** 1286,1316 ****
  									 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);
  	}
  }
--- 1006,1033 ----
  									 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);
  	}
  }
*************** explain_get_index_name(Oid indexId)
*** 1340,1342 ****
--- 1057,1168 ----
  	}
  	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);
+ 	}
+ }
diff --git a/src/backend/lib/stringinfo.c b/src/backend/lib/stringinfo.c
index b09f206..5ad884e 100644
*** a/src/backend/lib/stringinfo.c
--- b/src/backend/lib/stringinfo.c
*************** appendStringInfoChar(StringInfo str, cha
*** 187,192 ****
--- 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
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1976648..6da1c50 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyExplainStmt(ExplainStmt *from)
*** 2876,2881 ****
--- 2876,2882 ----
  	COPY_NODE_FIELD(query);
  	COPY_SCALAR_FIELD(verbose);
  	COPY_SCALAR_FIELD(analyze);
+ 	COPY_SCALAR_FIELD(costs);
  
  	return newnode;
  }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b466f4..61f2679 100644
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalExplainStmt(ExplainStmt *a, Explai
*** 1468,1473 ****
--- 1468,1474 ----
  	COMPARE_NODE_FIELD(query);
  	COMPARE_SCALAR_FIELD(verbose);
  	COMPARE_SCALAR_FIELD(analyze);
+ 	COMPARE_SCALAR_FIELD(costs);
  
  	return true;
  }
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 9ed9018..83d4248 100644
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 17,24 ****
--- 17,26 ----
  
  #include "catalog/pg_type.h"
  #include "nodes/makefuncs.h"
+ #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  
+ static bool parseBooleanOption(DefElem *opt);
  
  /*
   * makeA_Expr -
*************** makeDefElemExtended(char *nameSpace, cha
*** 385,387 ****
--- 387,460 ----
  
  	return res;
  }
+ 
+ /*
+  * makeExplain -
+  *  build an ExplainStmt node by parsing the generic options list
+  */
+ ExplainStmt *
+ makeExplain(List *options, Node *query)
+ {
+ 	ExplainStmt *n = makeNode(ExplainStmt);
+ 	ListCell *lc;
+ 
+ 	n->costs = true;
+ 	n->query = query;
+ 
+ 	foreach (lc, options)
+ 	{
+ 		DefElem *opt = lfirst(lc);
+ 		if (!strcmp(opt->defname, "analyze"))
+ 			n->analyze = parseBooleanOption(opt);
+ 		else if (!strcmp(opt->defname, "verbose"))
+ 			n->verbose = parseBooleanOption(opt);
+ 		else if (!strcmp(opt->defname, "costs"))
+ 			n->costs = parseBooleanOption(opt);
+ 		else
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+ 				 errmsg("unknown EXPLAIN option: %s", opt->defname)));
+ 	}
+ 
+ 	return n;
+ }
+ 
+ /*
+  * parseBooleanOption -
+  * 	Interpret a DefElem option as a boolean.
+  */
+ static bool
+ parseBooleanOption(DefElem *opt)
+ {
+ 	bool res;
+ 
+ 	/*
+ 	 * We interpret an omitted boolean argument as equivalent to "true", so
+ 	 * that, for example, EXPLAIN (ANALYZE) means the same thing as
+ 	 * EXPLAIN (ANALYZE ON).
+ 	 */
+ 	if (!opt->arg)
+ 	{
+ 		return true;
+ 	}
+ 	else if (IsA(opt->arg, Integer))
+ 	{
+ 		if (intVal(opt->arg) == 0)
+ 			return false;
+ 		else if (intVal(opt->arg) == 1)
+ 			return true;
+ 	}
+ 	else if (IsA(opt->arg, String))
+ 	{
+ 		if (parse_bool(strVal(opt->arg), &res))
+ 			return res;
+ 	}
+ 
+ 	ereport(ERROR,
+ 		(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 		 errmsg("parameter \"%s\" requires a Boolean value",
+ 			opt->defname)));
+ 
+ 	/* silence compiler warning */
+ 	return false;
+ }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 858e16c..1d51032 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static TypeName *TableFuncTypeName(List 
*** 369,374 ****
--- 369,379 ----
  %type <defelt>	generic_option_elem alter_generic_option_elem
  %type <list>	generic_option_list alter_generic_option_list
  
+ %type <str>		explain_option_name
+ %type <node>	explain_option_arg
+ %type <defelt>	explain_option_elem
+ %type <list>	explain_option_list
+ 
  %type <typnam>	Typename SimpleTypename ConstTypename
  				GenericType Numeric opt_float
  				Character ConstCharacter
*************** opt_name_list:
*** 6447,6463 ****
   *
   *		QUERY:
   *				EXPLAIN [ANALYZE] [VERBOSE] query
   *
   *****************************************************************************/
  
  ExplainStmt: EXPLAIN opt_analyze opt_verbose ExplainableStmt
  				{
! 					ExplainStmt *n = makeNode(ExplainStmt);
  					n->analyze = $2;
  					n->verbose = $3;
- 					n->query = $4;
  					$$ = (Node *)n;
  				}
  		;
  
  ExplainableStmt:
--- 6452,6472 ----
   *
   *		QUERY:
   *				EXPLAIN [ANALYZE] [VERBOSE] query
+  *				EXPLAIN ( options ) query
   *
   *****************************************************************************/
  
  ExplainStmt: EXPLAIN opt_analyze opt_verbose ExplainableStmt
  				{
! 					ExplainStmt *n = makeExplain(NIL, (Node *) $4);
  					n->analyze = $2;
  					n->verbose = $3;
  					$$ = (Node *)n;
  				}
+ 		|	EXPLAIN '(' explain_option_list ')' ExplainableStmt
+ 				{
+ 					$$ = (Node *) makeExplain((List *) $3, (Node *) $5);
+ 				}
  		;
  
  ExplainableStmt:
*************** ExplainableStmt:
*** 6470,6478 ****
  			| ExecuteStmt					/* by default all are $$=$1 */
  		;
  
  opt_analyze:
  			analyze_keyword			{ $$ = TRUE; }
! 			| /* EMPTY */			{ $$ = FALSE; }
  		;
  
  /*****************************************************************************
--- 6479,6529 ----
  			| ExecuteStmt					/* by default all are $$=$1 */
  		;
  
+ /*
+  * The precedence declaration for the opt_analyze EMPTY case, below, is
+  * necessary to prevent a shift/reduce conflict in the second production for
+  * ExplainStmt, above.  Otherwise, when the parser encounters "EXPLAIN (", it
+  * can't tell whether the "(" is the beginning of a SelectStmt or the beginning
+  * of the options list.  The precedence declaration below forces the latter
+  * interpretation.
+  *
+  * It might seem that we could get away with simply changing the definition of
+  * ExplainableStmt to use select_without_parens rather than SelectStmt, but
+  * that does not work, because select_without_parens produces expressions such
+  * as "(SELECT NULL) ORDER BY 1" that we interpret as legal queries.
+  */
  opt_analyze:
  			analyze_keyword			{ $$ = TRUE; }
! 			| /* EMPTY */			%prec UMINUS { $$ = FALSE; }
! 		;
! 
! explain_option_list:
! 			explain_option_elem
! 				{
! 					$$ = list_make1($1);
! 				}
! 			| explain_option_list ',' explain_option_elem
! 				{
! 					$$ = lappend($1, $3);
! 				}
! 		;
! 
! explain_option_elem:
! 			explain_option_name explain_option_arg
! 				{
! 					$$ = makeDefElem($1, $2);
! 				}
! 		;
! 
! explain_option_name:
! 				ColLabel			{ $$ = $1; }
! 		;
! 
! explain_option_arg:
! 			  opt_boolean			{ $$ = (Node *) makeString($1); }
! 			| ColId_or_Sconst		{ $$ = (Node *) makeString($1); }
! 			| SignedIconst			{ $$ = (Node *) makeInteger($1); }
! 			| /* EMPTY */			{ $$ = NULL; }
  		;
  
  /*****************************************************************************
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 333cb25..9005209 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** static RangeTblEntry *find_rte_by_refnam
*** 187,193 ****
  					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 ----
*************** isSimpleNode(Node *node, Node *parentNod
*** 4173,4188 ****
  
  
  /*
-  * 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 ----
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index aa3b643..238b654 100644
*** a/src/include/commands/explain.h
--- b/src/include/commands/explain.h
*************** extern void ExplainOnePlan(PlannedStmt *
*** 44,49 ****
  			   TupOutputState *tstate);
  
  extern void ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
! 				 bool analyze, bool verbose);
  
  #endif   /* EXPLAIN_H */
--- 44,49 ----
  			   TupOutputState *tstate);
  
  extern void ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
! 				 ExplainStmt *stmt);
  
  #endif   /* EXPLAIN_H */
diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h
index 17990c4..3932107 100644
*** a/src/include/lib/stringinfo.h
--- b/src/include/lib/stringinfo.h
*************** extern void appendStringInfoChar(StringI
*** 132,137 ****
--- 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.
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 7d24346..4ee826a 100644
*** a/src/include/nodes/makefuncs.h
--- b/src/include/nodes/makefuncs.h
*************** extern DefElem *makeDefElem(char *name, 
*** 69,72 ****
--- 69,74 ----
  extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
  					DefElemAction defaction);
  
+ extern ExplainStmt *makeExplain(List *options, Node *query);
+ 
  #endif   /* MAKEFUNC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9d53ab9..b7319c1 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct ExplainStmt
*** 2193,2199 ****
  	NodeTag		type;
  	Node	   *query;			/* the query (as a raw parse tree) */
  	bool		verbose;		/* print plan info */
! 	bool		analyze;		/* get statistics by executing plan */
  } ExplainStmt;
  
  /* ----------------------
--- 2193,2200 ----
  	NodeTag		type;
  	Node	   *query;			/* the query (as a raw parse tree) */
  	bool		verbose;		/* print plan info */
! 	bool		analyze;		/* actually execute plan */
! 	bool		costs;			/* print costs and times */
  } ExplainStmt;
  
  /* ----------------------
-- 
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