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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers