*** a/contrib/auto_explain/auto_explain.c
--- b/contrib/auto_explain/auto_explain.c
***************
*** 196,211 **** explain_ExecutorEnd(QueryDesc *queryDesc)
  		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')
! 				buf.data[--buf.len] = '\0';
  
  			/*
  			 * Note: we rely on the existing logging of context or
--- 196,212 ----
  		msec = queryDesc->totaltime->total * 1000.0;
  		if (msec >= auto_explain_log_min_duration)
  		{
! 			ExplainState	es;
  
! 			ExplainInitState(&es);
! 			es.analyze = (queryDesc->doInstrument && auto_explain_log_analyze);
! 			es.verbose = auto_explain_log_verbose;
! 			es.pstmt = queryDesc->plannedstmt;
! 			ExplainPrintPlan(&es, queryDesc);
  
  			/* Remove last line break */
! 			if (es.str.len > 0 && es.str.data[es.str.len - 1] == '\n')
! 				es.str.data[--es.str.len] = '\0';
  
  			/*
  			 * Note: we rely on the existing logging of context or
***************
*** 215,223 **** explain_ExecutorEnd(QueryDesc *queryDesc)
  			 */
  			ereport(LOG,
  					(errmsg("duration: %.3f ms  plan:\n%s",
! 							msec, buf.data)));
  
! 			pfree(buf.data);
  		}
  	}
  
--- 216,224 ----
  			 */
  			ereport(LOG,
  					(errmsg("duration: %.3f ms  plan:\n%s",
! 							msec, es.str.data)));
  
! 			pfree(es.str.data);
  		}
  	}
  
*** a/doc/src/sgml/ref/explain.sgml
--- b/doc/src/sgml/ref/explain.sgml
***************
*** 31,36 **** PostgreSQL documentation
--- 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>
***************
*** 70,75 **** EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replac
--- 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
***************
*** 99,105 **** ROLLBACK;
      <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>FALSE</command>.
       </para>
      </listitem>
     </varlistentry>
***************
*** 108,114 **** ROLLBACK;
      <term><literal>VERBOSE</literal></term>
      <listitem>
       <para>
!       Include the output column list for each node in the plan tree.
       </para>
      </listitem>
     </varlistentry>
--- 120,151 ----
      <term><literal>VERBOSE</literal></term>
      <listitem>
       <para>
!       Include the output column list for each node in the plan tree.  This
!       parameter defaults to <command>FALSE</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>TRUE</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> or <literal>1</literal> to
!       request the stated option, and <literal>FALSE</literal>
!       or <literal>0</literal>.  If the Boolean value is omitted, it defaults
!       to <literal>TRUE</literal>.
       </para>
      </listitem>
     </varlistentry>
***************
*** 202,207 **** EXPLAIN SELECT * FROM foo WHERE i = 4;
--- 239,258 ----
    </para>
  
    <para>
+    Here is the same plan with costs suppressed:
+ 
+ <programlisting>
+ EXPLAIN (COSTS FALSE) 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:
  
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 16,21 ****
--- 16,22 ----
  #include "access/xact.h"
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_type.h"
+ #include "commands/defrem.h"
  #include "commands/explain.h"
  #include "commands/prepare.h"
  #include "commands/trigger.h"
***************
*** 40,59 **** ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
  explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
  
  
! typedef struct ExplainState
! {
! 	StringInfo	str;			/* output buffer */
! 	/* options */
! 	bool		printTList;		/* print plan targetlists */
! 	bool		printAnalyze;	/* print actual times */
! 	/* other states */
! 	PlannedStmt *pstmt;			/* top of plan */
! 	List	   *rtable;			/* range table */
! } ExplainState;
! 
! static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
  				const char *queryString,
! 				ParamListInfo params, TupOutputState *tstate);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
  				StringInfo buf);
  static double elapsed_time(instr_time *starttime);
--- 41,49 ----
  explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
  
  
! static void ExplainOneQuery(Query *query, ExplainState *es,
  				const char *queryString,
! 				ParamListInfo params);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
  				StringInfo buf);
  static double elapsed_time(instr_time *starttime);
***************
*** 88,94 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
  	int			num_params;
  	TupOutputState *tstate;
  	List	   *rewritten;
! 	ListCell   *l;
  
  	/* Convert parameter type data to the form parser wants */
  	getParamListTypes(params, &param_types, &num_params);
--- 78,103 ----
  	int			num_params;
  	TupOutputState *tstate;
  	List	   *rewritten;
! 	ListCell   *lc;
! 	ExplainState es;
! 
! 	/* Initialize ExplainState. */
! 	ExplainInitState(&es);
! 
! 	/* Parse options list. */
! 	foreach (lc, stmt->options)
! 	{
! 		DefElem *opt = lfirst(lc);
! 		if (!strcmp(opt->defname, "analyze"))
! 			es.analyze = defGetBoolean(opt);
! 		else if (!strcmp(opt->defname, "verbose"))
! 			es.verbose = defGetBoolean(opt);
! 		else if (!strcmp(opt->defname, "costs"))
! 			es.costs = defGetBoolean(opt);
! 		else
! 			ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER),
! 				errmsg("unknown EXPLAIN option: %s", opt->defname)));
! 	}
  
  	/* Convert parameter type data to the form parser wants */
  	getParamListTypes(params, &param_types, &num_params);
***************
*** 106,136 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
  	rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
  									   queryString, param_types, num_params);
  
- 	/* prepare for projection of tuples */
- 	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
- 
  	if (rewritten == NIL)
  	{
  		/* In the case of an INSTEAD NOTHING, tell at least that */
! 		do_text_output_oneline(tstate, "Query rewrites to nothing");
  	}
  	else
  	{
  		/* Explain every plan */
  		foreach(l, rewritten)
  		{
! 			ExplainOneQuery((Query *) lfirst(l), stmt,
! 							queryString, params, tstate);
  			/* put a blank line between plans */
  			if (lnext(l) != NULL)
! 				do_text_output_oneline(tstate, "");
  		}
  	}
  
  	end_tup_output(tstate);
  }
  
  /*
   * ExplainResultDesc -
   *	  construct the result tupledesc for an EXPLAIN
   */
--- 115,157 ----
  	rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
  									   queryString, param_types, num_params);
  
  	if (rewritten == NIL)
  	{
  		/* In the case of an INSTEAD NOTHING, tell at least that */
! 		appendStringInfoString(&es.str, "Query rewrites to nothing\n");
  	}
  	else
  	{
+ 		ListCell   *l;
+ 
  		/* Explain every plan */
  		foreach(l, rewritten)
  		{
! 			ExplainOneQuery((Query *) lfirst(l), &es, queryString, params);
  			/* put a blank line between plans */
  			if (lnext(l) != NULL)
! 				appendStringInfoString(&es.str, "\n");
  		}
  	}
  
+ 	/* output tuples */
+ 	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
+ 	do_text_output_multiline(tstate, es.str.data);
  	end_tup_output(tstate);
  }
  
  /*
+  * Initialize ExplainState.
+  */
+ void
+ ExplainInitState(ExplainState *es)
+ {
+ 	memset(es, 0, sizeof(ExplainState));
+ 	es->costs = 1;
+ 	initStringInfo(&es->str);
+ }
+ 
+ /*
   * ExplainResultDesc -
   *	  construct the result tupledesc for an EXPLAIN
   */
***************
*** 151,179 **** ExplainResultDesc(ExplainStmt *stmt)
   *	  print out the execution plan for one Query
   */
  static void
! ExplainOneQuery(Query *query, ExplainStmt *stmt, const char *queryString,
! 				ParamListInfo params, TupOutputState *tstate)
  {
  	/* planner will not cope with utility statements */
  	if (query->commandType == CMD_UTILITY)
  	{
! 		ExplainOneUtility(query->utilityStmt, stmt,
! 						  queryString, params, tstate);
  		return;
  	}
  
  	/* if an advisor plugin is present, let it manage things */
  	if (ExplainOneQuery_hook)
! 		(*ExplainOneQuery_hook) (query, stmt, queryString, params, tstate);
  	else
  	{
- 		PlannedStmt *plan;
- 
  		/* plan the query */
! 		plan = pg_plan_query(query, 0, params);
  
  		/* run it (if needed) and produce output */
! 		ExplainOnePlan(plan, stmt, queryString, params, tstate);
  	}
  }
  
--- 172,197 ----
   *	  print out the execution plan for one Query
   */
  static void
! ExplainOneQuery(Query *query, ExplainState *es, const char *queryString,
! 				ParamListInfo params)
  {
  	/* planner will not cope with utility statements */
  	if (query->commandType == CMD_UTILITY)
  	{
! 		ExplainOneUtility(query->utilityStmt, es, queryString, params);
  		return;
  	}
  
  	/* if an advisor plugin is present, let it manage things */
  	if (ExplainOneQuery_hook)
! 		(*ExplainOneQuery_hook) (query, es, queryString, params);
  	else
  	{
  		/* plan the query */
! 		es->pstmt = pg_plan_query(query, 0, params);
  
  		/* run it (if needed) and produce output */
! 		ExplainOnePlan(es, queryString, params);
  	}
  }
  
***************
*** 187,207 **** ExplainOneQuery(Query *query, ExplainStmt *stmt, const char *queryString,
   * EXPLAIN EXECUTE case
   */
  void
! ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
! 				  const char *queryString, ParamListInfo params,
! 				  TupOutputState *tstate)
  {
  	if (utilityStmt == NULL)
  		return;
  
  	if (IsA(utilityStmt, ExecuteStmt))
! 		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
! 							queryString, params, tstate);
  	else if (IsA(utilityStmt, NotifyStmt))
! 		do_text_output_oneline(tstate, "NOTIFY");
  	else
! 		do_text_output_oneline(tstate,
! 							   "Utility statements have no plan structure");
  }
  
  /*
--- 205,224 ----
   * EXPLAIN EXECUTE case
   */
  void
! ExplainOneUtility(Node *utilityStmt, ExplainState *es,
! 				  const char *queryString, ParamListInfo params)
  {
  	if (utilityStmt == NULL)
  		return;
  
  	if (IsA(utilityStmt, ExecuteStmt))
! 		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es,
! 							queryString, params);
  	else if (IsA(utilityStmt, NotifyStmt))
! 		appendStringInfoString(&es->str, "NOTIFY\n");
  	else
! 		appendStringInfoString(&es->str,
! 							   "Utility statements have no plan structure\n");
  }
  
  /*
***************
*** 219,232 **** ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
   * to call it.
   */
  void
! ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
! 			   const char *queryString, ParamListInfo params,
! 			   TupOutputState *tstate)
  {
  	QueryDesc  *queryDesc;
  	instr_time	starttime;
  	double		totaltime = 0;
- 	StringInfoData buf;
  	int			eflags;
  
  	/*
--- 236,246 ----
   * to call it.
   */
  void
! ExplainOnePlan(ExplainState *es, const char *queryString, ParamListInfo params)
  {
  	QueryDesc  *queryDesc;
  	instr_time	starttime;
  	double		totaltime = 0;
  	int			eflags;
  
  	/*
***************
*** 236,254 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  	PushUpdatedSnapshot(GetActiveSnapshot());
  
  	/* Create a QueryDesc requesting no output */
! 	queryDesc = CreateQueryDesc(plannedstmt, queryString,
  								GetActiveSnapshot(), InvalidSnapshot,
! 								None_Receiver, params,
! 								stmt->analyze);
  
  	INSTR_TIME_SET_CURRENT(starttime);
  
  	/* If analyzing, we need to cope with queued triggers */
! 	if (stmt->analyze)
  		AfterTriggerBeginQuery();
  
  	/* Select execution options */
! 	if (stmt->analyze)
  		eflags = 0;				/* default run-to-completion flags */
  	else
  		eflags = EXEC_FLAG_EXPLAIN_ONLY;
--- 250,267 ----
  	PushUpdatedSnapshot(GetActiveSnapshot());
  
  	/* Create a QueryDesc requesting no output */
! 	queryDesc = CreateQueryDesc(es->pstmt, queryString,
  								GetActiveSnapshot(), InvalidSnapshot,
! 								None_Receiver, params, es->analyze);
  
  	INSTR_TIME_SET_CURRENT(starttime);
  
  	/* If analyzing, we need to cope with queued triggers */
! 	if (es->analyze)
  		AfterTriggerBeginQuery();
  
  	/* Select execution options */
! 	if (es->analyze)
  		eflags = 0;				/* default run-to-completion flags */
  	else
  		eflags = EXEC_FLAG_EXPLAIN_ONLY;
***************
*** 257,263 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  	ExecutorStart(queryDesc, eflags);
  
  	/* Execute the plan for statistics if asked for */
! 	if (stmt->analyze)
  	{
  		/* run the plan */
  		ExecutorRun(queryDesc, ForwardScanDirection, 0L);
--- 270,276 ----
  	ExecutorStart(queryDesc, eflags);
  
  	/* Execute the plan for statistics if asked for */
! 	if (es->analyze)
  	{
  		/* run the plan */
  		ExecutorRun(queryDesc, ForwardScanDirection, 0L);
***************
*** 267,281 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  	}
  
  	/* Create textual dump of plan tree */
! 	initStringInfo(&buf);
! 	ExplainPrintPlan(&buf, queryDesc, stmt->analyze, stmt->verbose);
  
  	/*
  	 * If we ran the command, run any AFTER triggers it queued.  (Note this
  	 * will not include DEFERRED triggers; since those don't run until end of
  	 * transaction, we can't measure them.)  Include into total runtime.
  	 */
! 	if (stmt->analyze)
  	{
  		INSTR_TIME_SET_CURRENT(starttime);
  		AfterTriggerEndQuery(queryDesc->estate);
--- 280,293 ----
  	}
  
  	/* Create textual dump of plan tree */
! 	ExplainPrintPlan(es, queryDesc);
  
  	/*
  	 * If we ran the command, run any AFTER triggers it queued.  (Note this
  	 * will not include DEFERRED triggers; since those don't run until end of
  	 * transaction, we can't measure them.)  Include into total runtime.
  	 */
! 	if (es->analyze)
  	{
  		INSTR_TIME_SET_CURRENT(starttime);
  		AfterTriggerEndQuery(queryDesc->estate);
***************
*** 283,289 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  	}
  
  	/* Print info about runtime of triggers */
! 	if (stmt->analyze)
  	{
  		ResultRelInfo *rInfo;
  		bool		show_relname;
--- 295,301 ----
  	}
  
  	/* Print info about runtime of triggers */
! 	if (es->analyze)
  	{
  		ResultRelInfo *rInfo;
  		bool		show_relname;
***************
*** 295,306 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  		show_relname = (numrels > 1 || targrels != NIL);
  		rInfo = queryDesc->estate->es_result_relations;
  		for (nr = 0; nr < numrels; rInfo++, nr++)
! 			report_triggers(rInfo, show_relname, &buf);
  
  		foreach(l, targrels)
  		{
  			rInfo = (ResultRelInfo *) lfirst(l);
! 			report_triggers(rInfo, show_relname, &buf);
  		}
  	}
  
--- 307,318 ----
  		show_relname = (numrels > 1 || targrels != NIL);
  		rInfo = queryDesc->estate->es_result_relations;
  		for (nr = 0; nr < numrels; rInfo++, nr++)
! 			report_triggers(rInfo, show_relname, &es->str);
  
  		foreach(l, targrels)
  		{
  			rInfo = (ResultRelInfo *) lfirst(l);
! 			report_triggers(rInfo, show_relname, &es->str);
  		}
  	}
  
***************
*** 317,361 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  	PopActiveSnapshot();
  
  	/* We need a CCI just in case query expanded to multiple plans */
! 	if (stmt->analyze)
  		CommandCounterIncrement();
  
  	totaltime += elapsed_time(&starttime);
  
! 	if (stmt->analyze)
! 		appendStringInfo(&buf, "Total runtime: %.3f ms\n",
  						 1000.0 * totaltime);
- 	do_text_output_multiline(tstate, buf.data);
- 
- 	pfree(buf.data);
  }
  
  /*
   * ExplainPrintPlan -
   *	  convert a QueryDesc's plan tree to text and append it to 'str'
   *
-  * 'analyze' means to include runtime instrumentation results
-  * 'verbose' means a verbose printout (currently, it shows targetlists)
-  *
   * 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.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);
  }
  
  /*
--- 329,358 ----
  	PopActiveSnapshot();
  
  	/* We need a CCI just in case query expanded to multiple plans */
! 	if (es->analyze)
  		CommandCounterIncrement();
  
  	totaltime += elapsed_time(&starttime);
  
! 	if (es->analyze)
! 		appendStringInfo(&es->str, "Total runtime: %.3f ms\n",
  						 1000.0 * totaltime);
  }
  
  /*
   * ExplainPrintPlan -
   *	  convert a QueryDesc's plan tree to text and append it to 'str'
   *
   * NB: will not work on utility statements
   */
  void
! ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
  {
! 	Assert(queryDesc->plannedstmt == es->pstmt);
  
+ 	es->rtable = queryDesc->plannedstmt->rtable;
  	ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate,
! 				NULL, 0, es);
  }
  
  /*
***************
*** 439,451 **** ExplainNode(Plan *plan, PlanState *planstate,
  	if (indent)
  	{
  		Assert(indent >= 2);
! 		appendStringInfoSpaces(es->str, 2 * indent - 4);
! 		appendStringInfoString(es->str, "->  ");
  	}
  
  	if (plan == NULL)
  	{
! 		appendStringInfoChar(es->str, '\n');
  		return;
  	}
  
--- 436,448 ----
  	if (indent)
  	{
  		Assert(indent >= 2);
! 		appendStringInfoSpaces(&es->str, 2 * indent - 4);
! 		appendStringInfoString(&es->str, "->  ");
  	}
  
  	if (plan == NULL)
  	{
! 		appendStringInfoChar(&es->str, '\n');
  		return;
  	}
  
***************
*** 665,677 **** ExplainNode(Plan *plan, PlanState *planstate,
  			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:
--- 662,674 ----
  			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:
***************
*** 685,700 **** ExplainNode(Plan *plan, PlanState *planstate,
  			ExplainScanTarget((Scan *) plan, es);
  			break;
  		case T_BitmapIndexScan:
! 			appendStringInfo(es->str, " on %s",
  				explain_get_index_name(((BitmapIndexScan *) plan)->indexid));
  			break;
  		default:
  			break;
  	}
  
! 	appendStringInfo(es->str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
! 					 plan->startup_cost, plan->total_cost,
! 					 plan->plan_rows, plan->plan_width);
  
  	/*
  	 * We have to forcibly clean up the instrumentation state because we
--- 682,698 ----
  			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->costs)
! 		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
***************
*** 707,725 **** ExplainNode(Plan *plan, PlanState *planstate,
  	{
  		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)
! 		appendStringInfoString(es->str, " (never executed)");
! 	appendStringInfoChar(es->str, '\n');
  
  	/* target list */
! 	if (es->printTList)
  		show_plan_tlist(plan, indent, es);
  
  	/* quals, sort keys, etc */
--- 705,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->analyze)
! 		appendStringInfo(&es->str, " (never executed)");
! 	appendStringInfoChar(&es->str, '\n');
  
  	/* target list */
! 	if (es->verbose)
  		show_plan_tlist(plan, indent, es);
  
  	/* quals, sort keys, etc */
***************
*** 889,896 **** show_plan_tlist(Plan *plan, int indent, ExplainState *es)
  	useprefix = list_length(es->rtable) > 1;
  
  	/* Emit line prefix */
! 	appendStringInfoSpaces(es->str, indent * 2);
! 	appendStringInfoString(es->str, "  Output: ");
  
  	/* Deparse each non-junk result column */
  	i = 0;
--- 887,894 ----
  	useprefix = list_length(es->rtable) > 1;
  
  	/* Emit line prefix */
! 	appendStringInfoSpaces(&es->str, indent * 2);
! 	appendStringInfoString(&es->str, "  Output: ");
  
  	/* Deparse each non-junk result column */
  	i = 0;
***************
*** 901,913 **** show_plan_tlist(Plan *plan, int indent, ExplainState *es)
  		if (tle->resjunk)
  			continue;
  		if (i++ > 0)
! 			appendStringInfoString(es->str, ", ");
! 		appendStringInfoString(es->str,
  							   deparse_expression((Node *) tle->expr, context,
  												  useprefix, false));
  	}
  
! 	appendStringInfoChar(es->str, '\n');
  }
  
  /*
--- 899,911 ----
  		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');
  }
  
  /*
***************
*** 940,948 **** show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan,
  	/* Deparse the expression */
  	exprstr = deparse_expression(node, context, useprefix, false);
  
! 	/* And add to es->str */
! 	appendStringInfoSpaces(es->str, indent * 2);
! 	appendStringInfo(es->str, "  %s: %s\n", qlabel, exprstr);
  }
  
  /*
--- 938,946 ----
  	/* 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);
  }
  
  /*
***************
*** 988,995 **** show_sort_keys(Plan *sortplan, int indent, ExplainState *es)
  	if (nkeys <= 0)
  		return;
  
! 	appendStringInfoSpaces(es->str, indent * 2);
! 	appendStringInfoString(es->str, "  Sort Key: ");
  
  	/* Set up deparsing context */
  	context = deparse_context_for_plan((Node *) sortplan,
--- 986,993 ----
  	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,
***************
*** 1011,1021 **** show_sort_keys(Plan *sortplan, int indent, ExplainState *es)
  									 useprefix, true);
  		/* And add to es->str */
  		if (keyno > 0)
! 			appendStringInfoString(es->str, ", ");
! 		appendStringInfoString(es->str, exprstr);
  	}
  
! 	appendStringInfoChar(es->str, '\n');
  }
  
  /*
--- 1009,1019 ----
  									 useprefix, true);
  		/* And add to es->str */
  		if (keyno > 0)
! 			appendStringInfo(&es->str, ", ");
! 		appendStringInfoString(&es->str, exprstr);
  	}
  
! 	appendStringInfoChar(&es->str, '\n');
  }
  
  /*
***************
*** 1025,1038 **** 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);
  	}
  }
--- 1023,1036 ----
  show_sort_info(SortState *sortstate, int indent, ExplainState *es)
  {
  	Assert(IsA(sortstate, SortState));
! 	if (es->analyze && 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);
  	}
  }
***************
*** 1127,1137 **** ExplainScanTarget(Scan *plan, ExplainState *es)
  			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));
  }
  
--- 1125,1135 ----
  			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));
  }
  
***************
*** 1172,1179 **** ExplainSubPlans(List *plans, int indent, ExplainState *es)
  		SubPlanState *sps = (SubPlanState *) lfirst(lst);
  		SubPlan    *sp = (SubPlan *) sps->xprstate.expr;
  
! 		appendStringInfoSpaces(es->str, indent * 2);
! 		appendStringInfo(es->str, "  %s\n", sp->plan_name);
  		ExplainNode(exec_subplan_get_plan(es->pstmt, sp),
  					sps->planstate, NULL, indent + 4, es);
  	}
--- 1170,1177 ----
  		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/commands/prepare.c
--- b/src/backend/commands/prepare.c
***************
*** 641,649 **** DropAllPreparedStatements(void)
   * not the original PREPARE; we get the latter string from the plancache.
   */
  void
! ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
! 					const char *queryString,
! 					ParamListInfo params, TupOutputState *tstate)
  {
  	PreparedStatement *entry;
  	const char *query_string;
--- 641,648 ----
   * not the original PREPARE; we get the latter string from the plancache.
   */
  void
! ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
! 					const char *queryString, ParamListInfo params)
  {
  	PreparedStatement *entry;
  	const char *query_string;
***************
*** 707,726 **** ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
  				pstmt->intoClause = execstmt->into;
  			}
  
! 			ExplainOnePlan(pstmt, stmt, query_string,
! 						   paramLI, tstate);
  		}
  		else
  		{
! 			ExplainOneUtility((Node *) pstmt, stmt, query_string,
! 							  params, tstate);
  		}
  
  		/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
  
  		/* put a blank line between plans */
  		if (!is_last_query)
! 			do_text_output_oneline(tstate, "");
  	}
  
  	if (estate)
--- 706,724 ----
  				pstmt->intoClause = execstmt->into;
  			}
  
! 			es->pstmt = pstmt;
! 			ExplainOnePlan(es, query_string, paramLI);
  		}
  		else
  		{
! 			ExplainOneUtility((Node *) pstmt, es, query_string, params);
  		}
  
  		/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
  
  		/* put a blank line between plans */
  		if (!is_last_query)
! 			appendStringInfo(&es->str, "\n");
  	}
  
  	if (estate)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2875,2882 **** _copyExplainStmt(ExplainStmt *from)
  	ExplainStmt *newnode = makeNode(ExplainStmt);
  
  	COPY_NODE_FIELD(query);
! 	COPY_SCALAR_FIELD(verbose);
! 	COPY_SCALAR_FIELD(analyze);
  
  	return newnode;
  }
--- 2875,2881 ----
  	ExplainStmt *newnode = makeNode(ExplainStmt);
  
  	COPY_NODE_FIELD(query);
! 	COPY_NODE_FIELD(options);
  
  	return newnode;
  }
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1467,1474 **** static bool
  _equalExplainStmt(ExplainStmt *a, ExplainStmt *b)
  {
  	COMPARE_NODE_FIELD(query);
! 	COMPARE_SCALAR_FIELD(verbose);
! 	COMPARE_SCALAR_FIELD(analyze);
  
  	return true;
  }
--- 1467,1473 ----
  _equalExplainStmt(ExplainStmt *a, ExplainStmt *b)
  {
  	COMPARE_NODE_FIELD(query);
! 	COMPARE_NODE_FIELD(options);
  
  	return true;
  }
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 17,25 ****
  
  #include "catalog/pg_type.h"
  #include "nodes/makefuncs.h"
  #include "utils/lsyscache.h"
  
- 
  /*
   * makeA_Expr -
   *		makes an A_Expr node
--- 17,25 ----
  
  #include "catalog/pg_type.h"
  #include "nodes/makefuncs.h"
+ #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  
  /*
   * makeA_Expr -
   *		makes an A_Expr node
***************
*** 385,387 **** makeDefElemExtended(char *nameSpace, char *name, Node *arg,
--- 385,402 ----
  
  	return res;
  }
+ 
+ /*
+  * makeExplain -
+  *  build an ExplainStmt node
+  */
+ ExplainStmt *
+ makeExplain(Node *query, List *options)
+ {
+ 	ExplainStmt *n = makeNode(ExplainStmt);
+ 
+ 	n->query = query;
+ 	n->options = options;
+ 
+ 	return n;
+ }
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 321,327 **** static TypeName *TableFuncTypeName(List *columns);
  %type <list>	opt_interval interval_second
  %type <node>	overlay_placing substr_from substr_for
  
! %type <boolean> opt_instead opt_analyze
  %type <boolean> index_opt_unique opt_verbose opt_full
  %type <boolean> opt_freeze opt_default opt_recheck
  %type <defelt>	opt_binary opt_oids copy_delimiter
--- 321,327 ----
  %type <list>	opt_interval interval_second
  %type <node>	overlay_placing substr_from substr_for
  
! %type <boolean> opt_instead
  %type <boolean> index_opt_unique opt_verbose opt_full
  %type <boolean> opt_freeze opt_default opt_recheck
  %type <defelt>	opt_binary opt_oids copy_delimiter
***************
*** 369,374 **** static TypeName *TableFuncTypeName(List *columns);
--- 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
***************
*** 6469,6484 **** opt_name_list:
   *
   *		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;
  				}
  		;
  
--- 6474,6503 ----
   *
   *		QUERY:
   *				EXPLAIN [ANALYZE] [VERBOSE] query
+  *				EXPLAIN ( options ) query
   *
   *****************************************************************************/
  
! ExplainStmt:
! 		EXPLAIN ExplainableStmt
  				{
! 					$$ = (Node *) makeExplain((Node *) $2, NIL);
! 				}
! 		| EXPLAIN ANALYZE opt_verbose ExplainableStmt
! 				{
! 					List *l = list_make1(makeDefElem("analyze", NULL));
! 					if ($3)
! 						lappend(l, makeDefElem("verbose", NULL));
! 					$$ = (Node *) makeExplain((Node *) $4, l);
! 				}
! 		| EXPLAIN VERBOSE ExplainableStmt
! 				{
! 					List *l = list_make1(makeDefElem("verbose", NULL));
! 					$$ = (Node *) makeExplain((Node *) $3, l);
! 				}
! 		|	EXPLAIN '(' explain_option_list ')' ExplainableStmt
! 				{
! 					$$ = (Node *) makeExplain((Node *) $5, (List *) $3);
  				}
  		;
  
***************
*** 6492,6500 **** ExplainableStmt:
  			| ExecuteStmt					/* by default all are $$=$1 */
  		;
  
! opt_analyze:
! 			analyze_keyword			{ $$ = TRUE; }
! 			| /* EMPTY */			{ $$ = FALSE; }
  		;
  
  /*****************************************************************************
--- 6511,6545 ----
  			| ExecuteStmt					/* by default all are $$=$1 */
  		;
  
! 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:
! 				ColId			{ $$ = $1; }
! 			| VERBOSE			{ $$ = "verbose"; }
! 			| analyze_keyword	{ $$ = "analyze"; }
! 		;
! 
! explain_option_arg:
! 			  opt_boolean			{ $$ = (Node *) makeString($1); }
! 			| ColId_or_Sconst		{ $$ = (Node *) makeString($1); }
! 			| SignedIconst			{ $$ = (Node *) makeInteger($1); }
! 			| /* EMPTY */			{ $$ = NULL; }
  		;
  
  /*****************************************************************************
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 2316,2325 **** GetCommandLogLevel(Node *parsetree)
  		case T_ExplainStmt:
  			{
  				ExplainStmt *stmt = (ExplainStmt *) parsetree;
  
  				/* Look through an EXPLAIN ANALYZE to the contained stmt */
! 				if (stmt->analyze)
  					return GetCommandLogLevel(stmt->query);
  				/* Plain EXPLAIN isn't so interesting */
  				lev = LOGSTMT_ALL;
  			}
--- 2316,2334 ----
  		case T_ExplainStmt:
  			{
  				ExplainStmt *stmt = (ExplainStmt *) parsetree;
+ 				ListCell	*lc;
+ 				int			 analyze = 0;
  
  				/* Look through an EXPLAIN ANALYZE to the contained stmt */
! 				foreach (lc, stmt->options)
! 				{
! 					DefElem *opt = lfirst(lc);
! 					if (!strcmp(opt->defname, "analyze"))
! 							analyze = defGetBoolean(opt);
! 				}
! 				if (analyze)
  					return GetCommandLogLevel(stmt->query);
+ 
  				/* Plain EXPLAIN isn't so interesting */
  				lev = LOGSTMT_ALL;
  			}
*** a/src/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 15,26 ****
  
  #include "executor/executor.h"
  
  /* Hook for plugins to get control in ExplainOneQuery() */
  typedef void (*ExplainOneQuery_hook_type) (Query *query,
! 													   ExplainStmt *stmt,
  													 const char *queryString,
! 													   ParamListInfo params,
! 													 TupOutputState *tstate);
  extern PGDLLIMPORT ExplainOneQuery_hook_type ExplainOneQuery_hook;
  
  /* Hook for plugins to get control in explain_get_index_name() */
--- 15,37 ----
  
  #include "executor/executor.h"
  
+ typedef struct ExplainState
+ {
+ 	StringInfoData	str;			/* output buffer */
+ 	/* options */
+ 	bool			verbose;		/* print plan targetlists */
+ 	bool			analyze;		/* print actual times */
+ 	bool			costs;			/* print costs */
+ 	/* other states */
+ 	PlannedStmt	   *pstmt;			/* top of plan */
+ 	List		   *rtable;			/* range table */
+ } ExplainState;
+ 
  /* Hook for plugins to get control in ExplainOneQuery() */
  typedef void (*ExplainOneQuery_hook_type) (Query *query,
! 													   ExplainState *es,
  													 const char *queryString,
! 													   ParamListInfo params);
  extern PGDLLIMPORT ExplainOneQuery_hook_type ExplainOneQuery_hook;
  
  /* Hook for plugins to get control in explain_get_index_name() */
***************
*** 28,49 **** typedef const char *(*explain_get_index_name_hook_type) (Oid indexId);
  extern PGDLLIMPORT explain_get_index_name_hook_type explain_get_index_name_hook;
  
  
! extern void ExplainQuery(ExplainStmt *stmt, const char *queryString,
  			 ParamListInfo params, DestReceiver *dest);
  
  extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
  
! extern void ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
  				  const char *queryString,
! 				  ParamListInfo params,
! 				  TupOutputState *tstate);
  
! extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
! 			   const char *queryString,
! 			   ParamListInfo params,
! 			   TupOutputState *tstate);
  
! extern void ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
! 				 bool analyze, bool verbose);
  
  #endif   /* EXPLAIN_H */
--- 39,58 ----
  extern PGDLLIMPORT explain_get_index_name_hook_type explain_get_index_name_hook;
  
  
! extern void ExplainQuery(ExplainStmt *es, const char *queryString,
  			 ParamListInfo params, DestReceiver *dest);
  
+ extern void ExplainInitState(ExplainState *es);
+ 
  extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
  
! extern void ExplainOneUtility(Node *utilityStmt, ExplainState *es,
  				  const char *queryString,
! 				  ParamListInfo params);
  
! extern void ExplainOnePlan(ExplainState *es, const char *queryString,
! 			   ParamListInfo params);
  
! extern void ExplainPrintPlan(ExplainState *stmt, QueryDesc *queryDesc);
  
  #endif   /* EXPLAIN_H */
*** a/src/include/commands/prepare.h
--- b/src/include/commands/prepare.h
***************
*** 13,18 ****
--- 13,19 ----
  #ifndef PREPARE_H
  #define PREPARE_H
  
+ #include "commands/explain.h"
  #include "executor/executor.h"
  #include "utils/plancache.h"
  #include "utils/timestamp.h"
***************
*** 40,48 **** extern void ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
  			 ParamListInfo params,
  			 DestReceiver *dest, char *completionTag);
  extern void DeallocateQuery(DeallocateStmt *stmt);
! extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
  					const char *queryString,
! 					ParamListInfo params, TupOutputState *tstate);
  
  /* Low-level access to stored prepared statements */
  extern void StorePreparedStatement(const char *stmt_name,
--- 41,49 ----
  			 ParamListInfo params,
  			 DestReceiver *dest, char *completionTag);
  extern void DeallocateQuery(DeallocateStmt *stmt);
! extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
  					const char *queryString,
! 					ParamListInfo params);
  
  /* Low-level access to stored prepared statements */
  extern void StorePreparedStatement(const char *stmt_name,
*** a/src/include/nodes/makefuncs.h
--- b/src/include/nodes/makefuncs.h
***************
*** 69,72 **** extern DefElem *makeDefElem(char *name, Node *arg);
--- 69,74 ----
  extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
  					DefElemAction defaction);
  
+ extern ExplainStmt *makeExplain(Node *query, List *options);
+ 
  #endif   /* MAKEFUNC_H */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2193,2200 **** typedef struct ExplainStmt
  {
  	NodeTag		type;
  	Node	   *query;			/* the query (as a raw parse tree) */
! 	bool		verbose;		/* print plan info */
! 	bool		analyze;		/* get statistics by executing plan */
  } ExplainStmt;
  
  /* ----------------------
--- 2193,2199 ----
  {
  	NodeTag		type;
  	Node	   *query;			/* the query (as a raw parse tree) */
! 	List	   *options;		/* List of DefElem nodes */
  } ExplainStmt;
  
  /* ----------------------
