Here is an updated version of my "generic options for explain" patch.
Previous version here:

http://archives.postgresql.org/pgsql-hackers/2009-06/msg00866.php

This patch requires the "explain refactoring v4" patch, which you can
find here, to be applied first:

http://archives.postgresql.org/pgsql-hackers/2009-06/msg00865.php

In this version, I've taken the liberty of adding a "COSTS" option
which defaults to "ON", so that you can say: EXPLAIN (COSTS OFF) ...
to abolish display of the costs information, per my previous
suggestion.  I was initially thinking of waiting to submit this as a
follow-on patch, but nobody seemed to object to the idea much, so I've
gone ahead and added it here.  It remains to be seen whether someone
can develop a workable set of regression tests based on this
functionality, but it's pretty clear that it CAN'T be done without
this functionality, so this seems like a step in the right direction
at any rate.

The other major update in this patch is that it adds documentation.  I
was not completely sure what the best way to document this was, so
it's very possible that what I've done here can be improved upon.

I will send updated versions of the "machine-readable explain output"
patches soon.

...Robert
*** 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;
***************
*** 196,207 **** 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')
--- 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')
*** 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>OFF</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,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>
***************
*** 202,207 **** EXPLAIN SELECT * FROM foo WHERE i = 4;
--- 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:
  
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 46,51 **** typedef struct ExplainState
--- 46,52 ----
  	/* 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 */
***************
*** 268,274 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  
  	/* Create textual dump of plan tree */
  	initStringInfo(&buf);
! 	ExplainPrintPlan(&buf, queryDesc, stmt->analyze, stmt->verbose);
  
  	/*
  	 * If we ran the command, run any AFTER triggers it queued.  (Note this
--- 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
***************
*** 340,347 **** ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
   * NB: will not work on utility statements
   */
  void
! ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
! 				 bool analyze, bool verbose)
  {
  	ExplainState es;
  
--- 341,347 ----
   * NB: will not work on utility statements
   */
  void
! ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc, ExplainStmt *stmt)
  {
  	ExplainState es;
  
***************
*** 349,356 **** ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc,
  
  	memset(&es, 0, sizeof(es));
  	es.str = str;
! 	es.printTList = verbose;
! 	es.printAnalyze = analyze;
  	es.pstmt = queryDesc->plannedstmt;
  	es.rtable = queryDesc->plannedstmt->rtable;
  
--- 349,357 ----
  
  	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;
  
***************
*** 688,696 **** ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan,
  			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
--- 689,698 ----
  			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
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2876,2881 **** _copyExplainStmt(ExplainStmt *from)
--- 2876,2882 ----
  	COPY_NODE_FIELD(query);
  	COPY_SCALAR_FIELD(verbose);
  	COPY_SCALAR_FIELD(analyze);
+ 	COPY_SCALAR_FIELD(costs);
  
  	return newnode;
  }
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1468,1473 **** _equalExplainStmt(ExplainStmt *a, ExplainStmt *b)
--- 1468,1474 ----
  	COMPARE_NODE_FIELD(query);
  	COMPARE_SCALAR_FIELD(verbose);
  	COMPARE_SCALAR_FIELD(analyze);
+ 	COMPARE_SCALAR_FIELD(costs);
  
  	return true;
  }
*** 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 -
***************
*** 385,387 **** makeDefElemExtended(char *namespace, char *name, Node *arg,
--- 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;
+ }
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 366,371 **** static TypeName *TableFuncTypeName(List *columns);
--- 366,376 ----
  %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
***************
*** 6441,6457 **** 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;
  				}
  		;
  
  ExplainableStmt:
--- 6446,6466 ----
   *
   *		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:
***************
*** 6464,6472 **** ExplainableStmt:
  			| ExecuteStmt					/* by default all are $$=$1 */
  		;
  
  opt_analyze:
  			analyze_keyword			{ $$ = TRUE; }
! 			| /* EMPTY */			{ $$ = FALSE; }
  		;
  
  /*****************************************************************************
--- 6473,6523 ----
  			| 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; }
  		;
  
  /*****************************************************************************
*** a/src/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 44,49 **** extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainStmt *stmt,
  			   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 */
*** 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(List *options, Node *query);
+ 
  #endif   /* MAKEFUNC_H */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2192,2198 **** 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;
  
  /* ----------------------
--- 2192,2199 ----
  	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