Hi Robert, Hi All, Patch applies with some offset changes, code changes look sensible, I personally like the new syntax and the features it may allow in future. One, possibly big, gripe remains though: The formerly valid statement which cannot be written without the parentheses and stay semantically equivalent: EXPLAIN (SELECT 1 ORDER BY 1) UNION ALL (SELECT 2 ORDER BY 1); is now not valid anymore (The added %prec UMINUS causes the first '(' to be recognize as start of the option list as intended). This currently can only be resolved by using an option list like: EXPLAIN (VERBOSE OFF) ... Its also currently impossible to use an empty set of parentheses to resolve this - this could easily be changed though.
I have to admit I don't see a nice solution here except living with the incompatibility... Perhaps somebody has a better idea? Andres PS: The 'offset corrected' version I tested with is attached
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index b7c0ef9..956af94 100644 *** a/contrib/auto_explain/auto_explain.c --- b/contrib/auto_explain/auto_explain.c *************** *** 14,19 **** --- 14,20 ---- #include "commands/explain.h" #include "executor/instrument.h" + #include "nodes/makefuncs.h" #include "utils/guc.h" PG_MODULE_MAGIC; *************** explain_ExecutorEnd(QueryDesc *queryDesc *** 196,207 **** msec = queryDesc->totaltime->total * 1000.0; if (msec >= auto_explain_log_min_duration) { StringInfoData buf; initStringInfo(&buf); ! ExplainPrintPlan(&buf, queryDesc, ! queryDesc->doInstrument && auto_explain_log_analyze, ! auto_explain_log_verbose); /* Remove last line break */ if (buf.len > 0 && buf.data[buf.len - 1] == '\n') --- 197,210 ---- msec = queryDesc->totaltime->total * 1000.0; if (msec >= auto_explain_log_min_duration) { + ExplainStmt *stmt = makeExplain(NIL, NULL); StringInfoData buf; initStringInfo(&buf); ! stmt->analyze = ! (queryDesc->doInstrument && auto_explain_log_analyze); ! stmt->verbose = auto_explain_log_verbose; ! ExplainPrintPlan(&buf, queryDesc, stmt); /* Remove last line break */ if (buf.len > 0 && buf.data[buf.len - 1] == '\n') diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml index ea9b3a6..afcab08 100644 *** a/doc/src/sgml/ref/explain.sgml --- b/doc/src/sgml/ref/explain.sgml *************** PostgreSQL documentation *** 31,36 **** --- 31,37 ---- <refsynopsisdiv> <synopsis> + EXPLAIN [ ( [ { ANALYZE | VERBOSE | COSTS } [ <replaceable class="parameter">boolean_value</replaceable> ] ] [, ...] ) ] <replaceable class="parameter">statement</replaceable> EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replaceable> </synopsis> </refsynopsisdiv> *************** EXPLAIN [ ANALYZE ] [ VERBOSE ] <replace *** 70,75 **** --- 71,86 ---- are close to reality. </para> + <para> + Only the <literal>ANALYZE</literal> and <literal>VERBOSE</literal> options + can be specified, and only in the order, without surrounding the option list + in parentheses. Prior to <productname>PostgreSQL</productname> 8.5, the + unparenthesized syntax was the only one supported. It is expected that + all new options will be supported only when using the parenthesized syntax, + which also allows a value to be specified for each option + (e.g. <literal>TRUE</literal> or <literal>FALSE</literal>). + </para> + <important> <para> Keep in mind that the statement is actually executed when *************** ROLLBACK; *** 99,105 **** <term><literal>ANALYZE</literal></term> <listitem> <para> ! Carry out the command and show the actual run times. </para> </listitem> </varlistentry> --- 110,117 ---- <term><literal>ANALYZE</literal></term> <listitem> <para> ! Carry out the command and show the actual run times. This ! parameter defaults to <command>OFF</command>. </para> </listitem> </varlistentry> *************** ROLLBACK; *** 108,114 **** <term><literal>VERBOSE</literal></term> <listitem> <para> ! Include the output column list for each node in the plan tree. </para> </listitem> </varlistentry> --- 120,152 ---- <term><literal>VERBOSE</literal></term> <listitem> <para> ! Include the output column list for each node in the plan tree. This ! parameter defaults to <command>OFF</command>. ! </para> ! </listitem> ! </varlistentry> ! ! <varlistentry> ! <term><literal>COSTS</literal></term> ! <listitem> ! <para> ! Include information on the estimated startup and total cost of each ! plan node, as well as the estimated number of rows and the estimated ! width of each row. This parameter defaults to <command>ON</command>. ! </para> ! </listitem> ! </varlistentry> ! ! <varlistentry> ! <term><replaceable class="parameter" />boolean_value</replaceable></term> ! <listitem> ! <para> ! Specifies whether the named parameter should be turned on or off. You ! can use the values <literal>TRUE</literal>, <literal>ON</literal>, ! <literal>YES</literal>, or <literal>1</literal> to request the stated ! option, and <literal>FALSE</literal>, <literal>OFF</literal>, ! <literal>NO</literal>, or <literal>0</literal>. If the Boolean value ! is omitted, it defaults to <literal>TRUE</literal>. </para> </listitem> </varlistentry> *************** EXPLAIN SELECT * FROM foo WHERE i = 4; *** 202,207 **** --- 240,259 ---- </para> <para> + Here is the same plan with costs suppressed: + + <programlisting> + EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE i = 4; + + QUERY PLAN + ---------------------------- + Index Scan using fi on foo + Index Cond: (i = 4) + (2 rows) + </programlisting> + </para> + + <para> Here is an example of a query plan for a query using an aggregate function: diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 950a2f1..efe436f 100644 *** a/src/backend/commands/explain.c --- b/src/backend/commands/explain.c *************** explain_get_index_name_hook_type explain *** 42,50 **** --- 42,52 ---- typedef struct ExplainState { + StringInfo str; /* output buffer */ /* options */ bool printTList; /* print plan targetlists */ bool printAnalyze; /* print actual times */ + bool printCosts; /* print costs */ /* other states */ PlannedStmt *pstmt; /* top of plan */ List *rtable; /* range table */ *************** static void ExplainOneQuery(Query *query *** 56,78 **** static void report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf); static double elapsed_time(instr_time *starttime); ! static void explain_outNode(StringInfo str, ! Plan *plan, PlanState *planstate, ! Plan *outer_plan, ! int indent, ExplainState *es); ! static void show_plan_tlist(Plan *plan, ! StringInfo str, int indent, ExplainState *es); static void show_scan_qual(List *qual, const char *qlabel, ! int scanrelid, Plan *scan_plan, Plan *outer_plan, ! StringInfo str, int indent, ExplainState *es); static void show_upper_qual(List *qual, const char *qlabel, Plan *plan, ! StringInfo str, int indent, ExplainState *es); ! static void show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols, ! const char *qlabel, ! StringInfo str, int indent, ExplainState *es); ! static void show_sort_info(SortState *sortstate, ! StringInfo str, int indent, ExplainState *es); static const char *explain_get_index_name(Oid indexId); /* --- 58,80 ---- static void report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf); static double elapsed_time(instr_time *starttime); ! static void ExplainNode(Plan *plan, PlanState *planstate, ! Plan *outer_plan, int indent, ExplainState *es); ! static void show_plan_tlist(Plan *plan, int indent, ExplainState *es); ! static void show_qual(List *qual, const char *qlabel, Plan *plan, ! Plan *outer_plan, int indent, bool useprefix, ExplainState *es); static void show_scan_qual(List *qual, const char *qlabel, ! Plan *scan_plan, Plan *outer_plan, ! int indent, ExplainState *es); static void show_upper_qual(List *qual, const char *qlabel, Plan *plan, ! int indent, ExplainState *es); ! static void show_sort_keys(Plan *sortplan, int indent, ExplainState *es); ! static void show_sort_info(SortState *sortstate, int indent, ExplainState *es); static const char *explain_get_index_name(Oid indexId); + static void ExplainScanTarget(Scan *plan, ExplainState *es); + static void ExplainMemberNodes(List *plans, PlanState **planstate, + Plan *outer_plan, int indent, ExplainState *es); + static void ExplainSubNodes(List *plans, int indent, ExplainState *es); /* *************** ExplainOnePlan(PlannedStmt *plannedstmt, *** 267,273 **** /* Create textual dump of plan tree */ initStringInfo(&buf); ! ExplainPrintPlan(&buf, queryDesc, stmt->analyze, stmt->verbose); /* * If we ran the command, run any AFTER triggers it queued. (Note this --- 269,275 ---- /* Create textual dump of plan tree */ initStringInfo(&buf); ! ExplainPrintPlan(&buf, queryDesc, stmt); /* * If we ran the command, run any AFTER triggers it queued. (Note this *************** ExplainOnePlan(PlannedStmt *plannedstmt, *** 339,360 **** * NB: will not work on utility statements */ void ! ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc, ! bool analyze, bool verbose) { ExplainState es; Assert(queryDesc->plannedstmt != NULL); memset(&es, 0, sizeof(es)); ! es.printTList = verbose; ! es.printAnalyze = analyze; es.pstmt = queryDesc->plannedstmt; es.rtable = queryDesc->plannedstmt->rtable; ! explain_outNode(str, ! queryDesc->plannedstmt->planTree, queryDesc->planstate, ! NULL, 0, &es); } /* --- 341,362 ---- * NB: will not work on utility statements */ void ! ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc, ExplainStmt *stmt) { ExplainState es; Assert(queryDesc->plannedstmt != NULL); memset(&es, 0, sizeof(es)); ! es.str = str; ! es.printTList = stmt->verbose; ! es.printAnalyze = stmt->analyze; ! es.printCosts = stmt->costs; es.pstmt = queryDesc->plannedstmt; es.rtable = queryDesc->plannedstmt->rtable; ! ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate, ! NULL, 0, &es); } /* *************** elapsed_time(instr_time *starttime) *** 414,420 **** } /* ! * explain_outNode - * converts a Plan node into ascii string and appends it to 'str' * * planstate points to the executor state node corresponding to the plan node. --- 416,422 ---- } /* ! * ExplainNode - * converts a Plan node into ascii string and appends it to 'str' * * planstate points to the executor state node corresponding to the plan node. *************** elapsed_time(instr_time *starttime) *** 426,442 **** * deciphering runtime keys of an inner indexscan. */ static void ! explain_outNode(StringInfo str, ! Plan *plan, PlanState *planstate, ! Plan *outer_plan, ! int indent, ExplainState *es) { const char *pname; ! int i; if (plan == NULL) { ! appendStringInfoChar(str, '\n'); return; } --- 428,448 ---- * deciphering runtime keys of an inner indexscan. */ static void ! ExplainNode(Plan *plan, PlanState *planstate, Plan *outer_plan, ! int indent, ExplainState *es) { const char *pname; ! ! if (indent) ! { ! Assert(indent >= 2); ! appendStringInfoSpaces(es->str, 2 * indent - 4); ! appendStringInfoString(es->str, "-> "); ! } if (plan == NULL) { ! appendStringInfoChar(es->str, '\n'); return; } *************** explain_outNode(StringInfo str, *** 656,798 **** break; } ! appendStringInfoString(str, pname); switch (nodeTag(plan)) { case T_IndexScan: if (ScanDirectionIsBackward(((IndexScan *) plan)->indexorderdir)) ! appendStringInfoString(str, " Backward"); ! appendStringInfo(str, " using %s", explain_get_index_name(((IndexScan *) plan)->indexid)); /* FALL THRU */ case T_SeqScan: case T_BitmapHeapScan: case T_TidScan: - if (((Scan *) plan)->scanrelid > 0) - { - RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid, - es->rtable); - char *relname; - - /* Assume it's on a real relation */ - Assert(rte->rtekind == RTE_RELATION); - - /* We only show the rel name, not schema name */ - relname = get_rel_name(rte->relid); - - appendStringInfo(str, " on %s", - quote_identifier(relname)); - if (strcmp(rte->eref->aliasname, relname) != 0) - appendStringInfo(str, " %s", - quote_identifier(rte->eref->aliasname)); - } - break; - case T_BitmapIndexScan: - appendStringInfo(str, " on %s", - explain_get_index_name(((BitmapIndexScan *) plan)->indexid)); - break; case T_SubqueryScan: - if (((Scan *) plan)->scanrelid > 0) - { - RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid, - es->rtable); - - appendStringInfo(str, " %s", - quote_identifier(rte->eref->aliasname)); - } - break; case T_FunctionScan: - if (((Scan *) plan)->scanrelid > 0) - { - RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid, - es->rtable); - Node *funcexpr; - char *proname; - - /* Assert it's on a RangeFunction */ - Assert(rte->rtekind == RTE_FUNCTION); - - /* - * If the expression is still a function call, we can get the - * real name of the function. Otherwise, punt (this can - * happen if the optimizer simplified away the function call, - * for example). - */ - funcexpr = ((FunctionScan *) plan)->funcexpr; - if (funcexpr && IsA(funcexpr, FuncExpr)) - { - Oid funcid = ((FuncExpr *) funcexpr)->funcid; - - /* We only show the func name, not schema name */ - proname = get_func_name(funcid); - } - else - proname = rte->eref->aliasname; - - appendStringInfo(str, " on %s", - quote_identifier(proname)); - if (strcmp(rte->eref->aliasname, proname) != 0) - appendStringInfo(str, " %s", - quote_identifier(rte->eref->aliasname)); - } - break; case T_ValuesScan: - if (((Scan *) plan)->scanrelid > 0) - { - RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid, - es->rtable); - char *valsname; - - /* Assert it's on a values rte */ - Assert(rte->rtekind == RTE_VALUES); - - valsname = rte->eref->aliasname; - - appendStringInfo(str, " on %s", - quote_identifier(valsname)); - } - break; case T_CteScan: - if (((Scan *) plan)->scanrelid > 0) - { - RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid, - es->rtable); - - /* Assert it's on a non-self-reference CTE */ - Assert(rte->rtekind == RTE_CTE); - Assert(!rte->self_reference); - - appendStringInfo(str, " on %s", - quote_identifier(rte->ctename)); - if (strcmp(rte->eref->aliasname, rte->ctename) != 0) - appendStringInfo(str, " %s", - quote_identifier(rte->eref->aliasname)); - } - break; case T_WorkTableScan: ! if (((Scan *) plan)->scanrelid > 0) ! { ! RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid, ! es->rtable); ! ! /* Assert it's on a self-reference CTE */ ! Assert(rte->rtekind == RTE_CTE); ! Assert(rte->self_reference); ! ! appendStringInfo(str, " on %s", ! quote_identifier(rte->ctename)); ! if (strcmp(rte->eref->aliasname, rte->ctename) != 0) ! appendStringInfo(str, " %s", ! quote_identifier(rte->eref->aliasname)); ! } break; default: break; } ! appendStringInfo(str, " (cost=%.2f..%.2f rows=%.0f width=%d)", ! plan->startup_cost, plan->total_cost, ! plan->plan_rows, plan->plan_width); /* * We have to forcibly clean up the instrumentation state because we --- 662,698 ---- break; } ! appendStringInfoString(es->str, pname); switch (nodeTag(plan)) { case T_IndexScan: if (ScanDirectionIsBackward(((IndexScan *) plan)->indexorderdir)) ! appendStringInfoString(es->str, " Backward"); ! appendStringInfo(es->str, " using %s", explain_get_index_name(((IndexScan *) plan)->indexid)); /* FALL THRU */ case T_SeqScan: case T_BitmapHeapScan: case T_TidScan: case T_SubqueryScan: case T_FunctionScan: case T_ValuesScan: case T_CteScan: case T_WorkTableScan: ! ExplainScanTarget((Scan *) plan, es); ! break; ! case T_BitmapIndexScan: ! appendStringInfo(es->str, " on %s", ! explain_get_index_name(((BitmapIndexScan *) plan)->indexid)); break; default: break; } ! if (es->printCosts) ! appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)", ! plan->startup_cost, plan->total_cost, ! plan->plan_rows, plan->plan_width); /* * We have to forcibly clean up the instrumentation state because we *************** explain_outNode(StringInfo str, *** 805,871 **** { double nloops = planstate->instrument->nloops; ! appendStringInfo(str, " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)", 1000.0 * planstate->instrument->startup / nloops, 1000.0 * planstate->instrument->total / nloops, planstate->instrument->ntuples / nloops, planstate->instrument->nloops); } else if (es->printAnalyze) ! appendStringInfo(str, " (never executed)"); ! appendStringInfoChar(str, '\n'); /* target list */ if (es->printTList) ! show_plan_tlist(plan, str, indent, es); /* quals, sort keys, etc */ switch (nodeTag(plan)) { case T_IndexScan: show_scan_qual(((IndexScan *) plan)->indexqualorig, ! "Index Cond", ! ((Scan *) plan)->scanrelid, ! plan, outer_plan, ! str, indent, es); show_scan_qual(plan->qual, ! "Filter", ! ((Scan *) plan)->scanrelid, ! plan, outer_plan, ! str, indent, es); break; case T_BitmapIndexScan: show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig, ! "Index Cond", ! ((Scan *) plan)->scanrelid, ! plan, outer_plan, ! str, indent, es); break; case T_BitmapHeapScan: /* XXX do we want to show this in production? */ show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig, ! "Recheck Cond", ! ((Scan *) plan)->scanrelid, ! plan, outer_plan, ! str, indent, es); /* FALL THRU */ case T_SeqScan: case T_FunctionScan: case T_ValuesScan: case T_CteScan: case T_WorkTableScan: - show_scan_qual(plan->qual, - "Filter", - ((Scan *) plan)->scanrelid, - plan, outer_plan, - str, indent, es); - break; case T_SubqueryScan: show_scan_qual(plan->qual, ! "Filter", ! ((Scan *) plan)->scanrelid, ! plan, outer_plan, ! str, indent, es); break; case T_TidScan: { --- 705,751 ---- { double nloops = planstate->instrument->nloops; ! appendStringInfo(es->str, ! " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)", 1000.0 * planstate->instrument->startup / nloops, 1000.0 * planstate->instrument->total / nloops, planstate->instrument->ntuples / nloops, planstate->instrument->nloops); } else if (es->printAnalyze) ! appendStringInfo(es->str, " (never executed)"); ! appendStringInfoChar(es->str, '\n'); /* target list */ if (es->printTList) ! show_plan_tlist(plan, indent, es); /* quals, sort keys, etc */ switch (nodeTag(plan)) { case T_IndexScan: show_scan_qual(((IndexScan *) plan)->indexqualorig, ! "Index Cond", plan, outer_plan, indent, es); show_scan_qual(plan->qual, ! "Filter", plan, outer_plan, indent, es); break; case T_BitmapIndexScan: show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig, ! "Index Cond", plan, outer_plan, indent, es); break; case T_BitmapHeapScan: /* XXX do we want to show this in production? */ show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig, ! "Recheck Cond", plan, outer_plan, indent, es); /* FALL THRU */ case T_SeqScan: case T_FunctionScan: case T_ValuesScan: case T_CteScan: case T_WorkTableScan: case T_SubqueryScan: show_scan_qual(plan->qual, ! "Filter", plan, outer_plan, indent, es); break; case T_TidScan: { *************** explain_outNode(StringInfo str, *** 878,946 **** if (list_length(tidquals) > 1) tidquals = list_make1(make_orclause(tidquals)); show_scan_qual(tidquals, ! "TID Cond", ! ((Scan *) plan)->scanrelid, ! plan, outer_plan, ! str, indent, es); show_scan_qual(plan->qual, ! "Filter", ! ((Scan *) plan)->scanrelid, ! plan, outer_plan, ! str, indent, es); } break; case T_NestLoop: show_upper_qual(((NestLoop *) plan)->join.joinqual, ! "Join Filter", plan, ! str, indent, es); ! show_upper_qual(plan->qual, ! "Filter", plan, ! str, indent, es); break; case T_MergeJoin: show_upper_qual(((MergeJoin *) plan)->mergeclauses, ! "Merge Cond", plan, ! str, indent, es); show_upper_qual(((MergeJoin *) plan)->join.joinqual, ! "Join Filter", plan, ! str, indent, es); ! show_upper_qual(plan->qual, ! "Filter", plan, ! str, indent, es); break; case T_HashJoin: show_upper_qual(((HashJoin *) plan)->hashclauses, ! "Hash Cond", plan, ! str, indent, es); show_upper_qual(((HashJoin *) plan)->join.joinqual, ! "Join Filter", plan, ! str, indent, es); ! show_upper_qual(plan->qual, ! "Filter", plan, ! str, indent, es); break; case T_Agg: case T_Group: ! show_upper_qual(plan->qual, ! "Filter", plan, ! str, indent, es); break; case T_Sort: ! show_sort_keys(plan, ! ((Sort *) plan)->numCols, ! ((Sort *) plan)->sortColIdx, ! "Sort Key", ! str, indent, es); ! show_sort_info((SortState *) planstate, ! str, indent, es); break; case T_Result: show_upper_qual((List *) ((Result *) plan)->resconstantqual, ! "One-Time Filter", plan, ! str, indent, es); ! show_upper_qual(plan->qual, ! "Filter", plan, ! str, indent, es); break; default: break; --- 758,799 ---- if (list_length(tidquals) > 1) tidquals = list_make1(make_orclause(tidquals)); show_scan_qual(tidquals, ! "TID Cond", plan, outer_plan, indent, es); show_scan_qual(plan->qual, ! "Filter", plan, outer_plan, indent, es); } break; case T_NestLoop: show_upper_qual(((NestLoop *) plan)->join.joinqual, ! "Join Filter", plan, indent, es); ! show_upper_qual(plan->qual, "Filter", plan, indent, es); break; case T_MergeJoin: show_upper_qual(((MergeJoin *) plan)->mergeclauses, ! "Merge Cond", plan, indent, es); show_upper_qual(((MergeJoin *) plan)->join.joinqual, ! "Join Filter", plan, indent, es); ! show_upper_qual(plan->qual, "Filter", plan, indent, es); break; case T_HashJoin: show_upper_qual(((HashJoin *) plan)->hashclauses, ! "Hash Cond", plan, indent, es); show_upper_qual(((HashJoin *) plan)->join.joinqual, ! "Join Filter", plan, indent, es); ! show_upper_qual(plan->qual, "Filter", plan, indent, es); break; case T_Agg: case T_Group: ! show_upper_qual(plan->qual, "Filter", plan, indent, es); break; case T_Sort: ! show_sort_keys(plan, indent, es); ! show_sort_info((SortState *) planstate, indent, es); break; case T_Result: show_upper_qual((List *) ((Result *) plan)->resconstantqual, ! "One-Time Filter", plan, indent, es); ! show_upper_qual(plan->qual, "Filter", plan, indent, es); break; default: break; *************** explain_outNode(StringInfo str, *** 948,1081 **** /* initPlan-s */ if (plan->initPlan) ! { ! ListCell *lst; ! ! foreach(lst, planstate->initPlan) ! { ! SubPlanState *sps = (SubPlanState *) lfirst(lst); ! SubPlan *sp = (SubPlan *) sps->xprstate.expr; ! ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " %s\n", sp->plan_name); ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " -> "); ! explain_outNode(str, ! exec_subplan_get_plan(es->pstmt, sp), ! sps->planstate, ! NULL, ! indent + 4, es); ! } ! } /* lefttree */ if (outerPlan(plan)) { - for (i = 0; i < indent; i++) - appendStringInfo(str, " "); - appendStringInfo(str, " -> "); - /* * Ordinarily we don't pass down our own outer_plan value to our child * nodes, but in bitmap scan trees we must, since the bottom * BitmapIndexScan nodes may have outer references. */ ! explain_outNode(str, outerPlan(plan), ! outerPlanState(planstate), ! IsA(plan, BitmapHeapScan) ? outer_plan : NULL, ! indent + 3, es); } /* righttree */ if (innerPlan(plan)) { ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " -> "); ! explain_outNode(str, innerPlan(plan), ! innerPlanState(planstate), ! outerPlan(plan), ! indent + 3, es); ! } ! ! if (IsA(plan, Append)) ! { ! Append *appendplan = (Append *) plan; ! AppendState *appendstate = (AppendState *) planstate; ! ListCell *lst; ! int j; ! ! j = 0; ! foreach(lst, appendplan->appendplans) ! { ! Plan *subnode = (Plan *) lfirst(lst); ! ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " -> "); ! ! /* ! * Ordinarily we don't pass down our own outer_plan value to our ! * child nodes, but in an Append we must, since we might be ! * looking at an appendrel indexscan with outer references from ! * the member scans. ! */ ! explain_outNode(str, subnode, ! appendstate->appendplans[j], ! outer_plan, ! indent + 3, es); ! j++; ! } ! } ! ! if (IsA(plan, BitmapAnd)) ! { ! BitmapAnd *bitmapandplan = (BitmapAnd *) plan; ! BitmapAndState *bitmapandstate = (BitmapAndState *) planstate; ! ListCell *lst; ! int j; ! ! j = 0; ! foreach(lst, bitmapandplan->bitmapplans) ! { ! Plan *subnode = (Plan *) lfirst(lst); ! ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " -> "); ! ! explain_outNode(str, subnode, ! bitmapandstate->bitmapplans[j], ! outer_plan, /* pass down same outer plan */ ! indent + 3, es); ! j++; ! } } ! if (IsA(plan, BitmapOr)) ! { ! BitmapOr *bitmaporplan = (BitmapOr *) plan; ! BitmapOrState *bitmaporstate = (BitmapOrState *) planstate; ! ListCell *lst; ! int j; ! ! j = 0; ! foreach(lst, bitmaporplan->bitmapplans) ! { ! Plan *subnode = (Plan *) lfirst(lst); ! ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " -> "); ! ! explain_outNode(str, subnode, ! bitmaporstate->bitmapplans[j], ! outer_plan, /* pass down same outer plan */ ! indent + 3, es); ! j++; ! } } if (IsA(plan, SubqueryScan)) --- 801,846 ---- /* initPlan-s */ if (plan->initPlan) ! ExplainSubNodes(planstate->initPlan, indent, es); /* lefttree */ if (outerPlan(plan)) { /* * Ordinarily we don't pass down our own outer_plan value to our child * nodes, but in bitmap scan trees we must, since the bottom * BitmapIndexScan nodes may have outer references. */ ! ExplainNode(outerPlan(plan), outerPlanState(planstate), ! IsA(plan, BitmapHeapScan) ? outer_plan : NULL, ! indent + 3, es); } /* righttree */ if (innerPlan(plan)) { ! ExplainNode(innerPlan(plan), innerPlanState(planstate), ! outerPlan(plan), indent + 3, es); } ! switch (nodeTag(plan)) { ! case T_Append: ! ExplainMemberNodes(((Append *) plan)->appendplans, ! ((AppendState *) planstate)->appendplans, ! outer_plan, indent, es); ! break; ! case T_BitmapAnd: ! ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans, ! ((BitmapAndState *) planstate)->bitmapplans, ! outer_plan, indent, es); ! break; ! case T_BitmapOr: ! ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans, ! ((BitmapOrState *) planstate)->bitmapplans, ! outer_plan, indent, es); ! break; ! default: ! break; } if (IsA(plan, SubqueryScan)) *************** explain_outNode(StringInfo str, *** 1084,1130 **** SubqueryScanState *subquerystate = (SubqueryScanState *) planstate; Plan *subnode = subqueryscan->subplan; ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " -> "); ! ! explain_outNode(str, subnode, ! subquerystate->subplan, ! NULL, ! indent + 3, es); } /* subPlan-s */ if (planstate->subPlan) ! { ! ListCell *lst; ! ! foreach(lst, planstate->subPlan) ! { ! SubPlanState *sps = (SubPlanState *) lfirst(lst); ! SubPlan *sp = (SubPlan *) sps->xprstate.expr; ! ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " %s\n", sp->plan_name); ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " -> "); ! explain_outNode(str, ! exec_subplan_get_plan(es->pstmt, sp), ! sps->planstate, ! NULL, ! indent + 4, es); ! } ! } } /* * Show the targetlist of a plan node */ static void ! show_plan_tlist(Plan *plan, ! StringInfo str, int indent, ExplainState *es) { List *context; bool useprefix; --- 849,867 ---- SubqueryScanState *subquerystate = (SubqueryScanState *) planstate; Plan *subnode = subqueryscan->subplan; ! ExplainNode(subnode, subquerystate->subplan, NULL, indent + 3, es); } /* subPlan-s */ if (planstate->subPlan) ! ExplainSubNodes(planstate->subPlan, indent, es); } /* * Show the targetlist of a plan node */ static void ! show_plan_tlist(Plan *plan, int indent, ExplainState *es) { List *context; bool useprefix; *************** show_plan_tlist(Plan *plan, *** 1149,1157 **** useprefix = list_length(es->rtable) > 1; /* Emit line prefix */ ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " Output: "); /* Deparse each non-junk result column */ i = 0; --- 886,893 ---- useprefix = list_length(es->rtable) > 1; /* Emit line prefix */ ! appendStringInfoSpaces(es->str, indent * 2); ! appendStringInfo(es->str, " Output: "); /* Deparse each non-junk result column */ i = 0; *************** show_plan_tlist(Plan *plan, *** 1162,1192 **** if (tle->resjunk) continue; if (i++ > 0) ! appendStringInfo(str, ", "); ! appendStringInfoString(str, deparse_expression((Node *) tle->expr, context, useprefix, false)); } ! appendStringInfoChar(str, '\n'); } /* ! * Show a qualifier expression for a scan plan node * * Note: outer_plan is the referent for any OUTER vars in the scan qual; * this would be the outer side of a nestloop plan. Pass NULL if none. */ static void ! show_scan_qual(List *qual, const char *qlabel, ! int scanrelid, Plan *scan_plan, Plan *outer_plan, ! StringInfo str, int indent, ExplainState *es) { List *context; - bool useprefix; Node *node; char *exprstr; - int i; /* No work if empty qual */ if (qual == NIL) --- 898,925 ---- if (tle->resjunk) continue; if (i++ > 0) ! appendStringInfo(es->str, ", "); ! appendStringInfoString(es->str, deparse_expression((Node *) tle->expr, context, useprefix, false)); } ! appendStringInfoChar(es->str, '\n'); } /* ! * Show a qualifier expression * * Note: outer_plan is the referent for any OUTER vars in the scan qual; * this would be the outer side of a nestloop plan. Pass NULL if none. */ static void ! show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan, ! int indent, bool useprefix, ExplainState *es) { List *context; Node *node; char *exprstr; /* No work if empty qual */ if (qual == NIL) *************** show_scan_qual(List *qual, const char *q *** 1196,1214 **** node = (Node *) make_ands_explicit(qual); /* Set up deparsing context */ ! context = deparse_context_for_plan((Node *) scan_plan, (Node *) outer_plan, es->rtable, es->pstmt->subplans); - useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan)); /* Deparse the expression */ exprstr = deparse_expression(node, context, useprefix, false); /* And add to str */ ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " %s: %s\n", qlabel, exprstr); } /* --- 929,958 ---- node = (Node *) make_ands_explicit(qual); /* Set up deparsing context */ ! context = deparse_context_for_plan((Node *) plan, (Node *) outer_plan, es->rtable, es->pstmt->subplans); /* Deparse the expression */ exprstr = deparse_expression(node, context, useprefix, false); /* And add to str */ ! appendStringInfoSpaces(es->str, indent * 2); ! appendStringInfo(es->str, " %s: %s\n", qlabel, exprstr); ! } ! ! /* ! * Show a qualifier expression for a scan plan node ! */ ! static void ! show_scan_qual(List *qual, const char *qlabel, ! Plan *scan_plan, Plan *outer_plan, ! int indent, ExplainState *es) ! { ! bool useprefix = ! (outer_plan != NULL || IsA(scan_plan, SubqueryScan)); ! show_qual(qual, qlabel, scan_plan, outer_plan, indent, useprefix, es); } /* *************** show_scan_qual(List *qual, const char *q *** 1216,1270 **** */ static void show_upper_qual(List *qual, const char *qlabel, Plan *plan, ! StringInfo str, int indent, ExplainState *es) { ! List *context; ! bool useprefix; ! Node *node; ! char *exprstr; ! int i; ! ! /* No work if empty qual */ ! if (qual == NIL) ! return; ! ! /* Set up deparsing context */ ! context = deparse_context_for_plan((Node *) plan, ! NULL, ! es->rtable, ! es->pstmt->subplans); ! useprefix = list_length(es->rtable) > 1; ! ! /* Deparse the expression */ ! node = (Node *) make_ands_explicit(qual); ! exprstr = deparse_expression(node, context, useprefix, false); ! /* And add to str */ ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " %s: %s\n", qlabel, exprstr); } /* * Show the sort keys for a Sort node. */ static void ! show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols, ! const char *qlabel, ! StringInfo str, int indent, ExplainState *es) { List *context; bool useprefix; int keyno; char *exprstr; ! int i; if (nkeys <= 0) return; ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " %s: ", qlabel); /* Set up deparsing context */ context = deparse_context_for_plan((Node *) sortplan, --- 960,990 ---- */ static void show_upper_qual(List *qual, const char *qlabel, Plan *plan, ! int indent, ExplainState *es) { ! bool useprefix = list_length(es->rtable) > 1; ! show_qual(qual, qlabel, plan, NULL, indent, useprefix, es); } /* * Show the sort keys for a Sort node. */ static void ! show_sort_keys(Plan *sortplan, int indent, ExplainState *es) { List *context; bool useprefix; int keyno; char *exprstr; ! int nkeys = ((Sort *) sortplan)->numCols; ! AttrNumber *keycols = ((Sort *) sortplan)->sortColIdx; if (nkeys <= 0) return; ! appendStringInfoSpaces(es->str, indent * 2); ! appendStringInfoString(es->str, " Sort Key: "); /* Set up deparsing context */ context = deparse_context_for_plan((Node *) sortplan, *************** show_sort_keys(Plan *sortplan, int nkeys *** 1286,1316 **** useprefix, true); /* And add to str */ if (keyno > 0) ! appendStringInfo(str, ", "); ! appendStringInfoString(str, exprstr); } ! appendStringInfo(str, "\n"); } /* * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node */ static void ! show_sort_info(SortState *sortstate, ! StringInfo str, int indent, ExplainState *es) { Assert(IsA(sortstate, SortState)); if (es->printAnalyze && sortstate->sort_Done && sortstate->tuplesortstate != NULL) { char *sortinfo; - int i; sortinfo = tuplesort_explain((Tuplesortstate *) sortstate->tuplesortstate); ! for (i = 0; i < indent; i++) ! appendStringInfo(str, " "); ! appendStringInfo(str, " %s\n", sortinfo); pfree(sortinfo); } } --- 1006,1033 ---- useprefix, true); /* And add to str */ if (keyno > 0) ! appendStringInfo(es->str, ", "); ! appendStringInfoString(es->str, exprstr); } ! appendStringInfo(es->str, "\n"); } /* * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node */ static void ! show_sort_info(SortState *sortstate, int indent, ExplainState *es) { Assert(IsA(sortstate, SortState)); if (es->printAnalyze && sortstate->sort_Done && sortstate->tuplesortstate != NULL) { char *sortinfo; sortinfo = tuplesort_explain((Tuplesortstate *) sortstate->tuplesortstate); ! appendStringInfoSpaces(es->str, indent * 2); ! appendStringInfo(es->str, " %s\n", sortinfo); pfree(sortinfo); } } *************** explain_get_index_name(Oid indexId) *** 1340,1342 **** --- 1057,1168 ---- } return result; } + + /* + * Explain details for Scan nodes. + */ + static void + ExplainScanTarget(Scan *plan, ExplainState *es) + { + char *objectname = NULL; + Node *funcexpr; + RangeTblEntry *rte; + + if (plan->scanrelid <= 0) + return; + rte = rt_fetch(plan->scanrelid, es->rtable); + + switch (nodeTag(plan)) + { + case T_IndexScan: + case T_SeqScan: + case T_BitmapHeapScan: + case T_TidScan: + /* Assert it's on a real relation */ + Assert(rte->rtekind == RTE_RELATION); + objectname = get_rel_name(rte->relid); + break; + case T_FunctionScan: + /* Assert it's on a RangeFunction */ + Assert(rte->rtekind == RTE_FUNCTION); + + /* + * If the expression is still a function call, we can get the + * real name of the function. Otherwise, punt (this can + * happen if the optimizer simplified away the function call, + * for example). + */ + funcexpr = ((FunctionScan *) plan)->funcexpr; + if (funcexpr && IsA(funcexpr, FuncExpr)) + { + Oid funcid = ((FuncExpr *) funcexpr)->funcid; + objectname = get_func_name(funcid); + } + break; + case T_ValuesScan: + Assert(rte->rtekind == RTE_VALUES); + break; + case T_CteScan: + /* Assert it's on a non-self-reference CTE */ + Assert(rte->rtekind == RTE_CTE); + Assert(!rte->self_reference); + objectname = rte->ctename; + break; + case T_WorkTableScan: + /* Assert it's on a self-reference CTE */ + Assert(rte->rtekind == RTE_CTE); + Assert(rte->self_reference); + objectname = rte->ctename; + break; + default: + break; + } + + appendStringInfoString(es->str, " on"); + if (objectname != NULL) + appendStringInfo(es->str, " %s", quote_identifier(objectname)); + if (objectname == NULL || strcmp(rte->eref->aliasname, objectname) != 0) + appendStringInfo(es->str, " %s", + quote_identifier(rte->eref->aliasname)); + } + + /* + * Explain details for Append, BitmapAnd, or BitmapOr constutent plans. + * Ordinarily we don't pass down outer_plan value to our child nodes, but in + * an Append, BitmapAnd, or BitmapOr we must, since these nodes can have outer + * references from the member scans. + */ + static void + ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan, + int indent, ExplainState *es) + { + ListCell *lst; + int j = 0; + + foreach(lst, plans) + { + ExplainNode((Plan *) lfirst(lst), planstate[j], outer_plan, + indent + 3, es); + ++j; + } + } + + /* + * Explain a list of Subplans (or initPlans, which use SubPlan nodes). + */ + static void + ExplainSubNodes(List *plans, int indent, ExplainState *es) + { + ListCell *lst; + + foreach(lst, plans) + { + SubPlanState *sps = (SubPlanState *) lfirst(lst); + SubPlan *sp = (SubPlan *) sps->xprstate.expr; + + appendStringInfoSpaces(es->str, indent * 2); + appendStringInfo(es->str, " %s\n", sp->plan_name); + ExplainNode(exec_subplan_get_plan(es->pstmt, sp), + sps->planstate, NULL, indent + 4, es); + } + } diff --git a/src/backend/lib/stringinfo.c b/src/backend/lib/stringinfo.c index b09f206..5ad884e 100644 *** a/src/backend/lib/stringinfo.c --- b/src/backend/lib/stringinfo.c *************** appendStringInfoChar(StringInfo str, cha *** 187,192 **** --- 187,209 ---- } /* + * appendStringInfoSpaces + * + * Append spaces to a buffer. + */ + void + appendStringInfoSpaces(StringInfo str, int count) + { + /* Make more room if needed */ + enlargeStringInfo(str, count); + + /* OK, append the spaces */ + while (--count >= 0) + str->data[str->len++] = ' '; + str->data[str->len] = '\0'; + } + + /* * appendBinaryStringInfo * * Append arbitrary binary data to a StringInfo, allocating more space diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 1976648..6da1c50 100644 *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** _copyExplainStmt(ExplainStmt *from) *** 2876,2881 **** --- 2876,2882 ---- COPY_NODE_FIELD(query); COPY_SCALAR_FIELD(verbose); COPY_SCALAR_FIELD(analyze); + COPY_SCALAR_FIELD(costs); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 8b466f4..61f2679 100644 *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** _equalExplainStmt(ExplainStmt *a, Explai *** 1468,1473 **** --- 1468,1474 ---- COMPARE_NODE_FIELD(query); COMPARE_SCALAR_FIELD(verbose); COMPARE_SCALAR_FIELD(analyze); + COMPARE_SCALAR_FIELD(costs); return true; } diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 9ed9018..83d4248 100644 *** a/src/backend/nodes/makefuncs.c --- b/src/backend/nodes/makefuncs.c *************** *** 17,24 **** --- 17,26 ---- #include "catalog/pg_type.h" #include "nodes/makefuncs.h" + #include "utils/builtins.h" #include "utils/lsyscache.h" + static bool parseBooleanOption(DefElem *opt); /* * makeA_Expr - *************** makeDefElemExtended(char *nameSpace, cha *** 385,387 **** --- 387,460 ---- return res; } + + /* + * makeExplain - + * build an ExplainStmt node by parsing the generic options list + */ + ExplainStmt * + makeExplain(List *options, Node *query) + { + ExplainStmt *n = makeNode(ExplainStmt); + ListCell *lc; + + n->costs = true; + n->query = query; + + foreach (lc, options) + { + DefElem *opt = lfirst(lc); + if (!strcmp(opt->defname, "analyze")) + n->analyze = parseBooleanOption(opt); + else if (!strcmp(opt->defname, "verbose")) + n->verbose = parseBooleanOption(opt); + else if (!strcmp(opt->defname, "costs")) + n->costs = parseBooleanOption(opt); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("unknown EXPLAIN option: %s", opt->defname))); + } + + return n; + } + + /* + * parseBooleanOption - + * Interpret a DefElem option as a boolean. + */ + static bool + parseBooleanOption(DefElem *opt) + { + bool res; + + /* + * We interpret an omitted boolean argument as equivalent to "true", so + * that, for example, EXPLAIN (ANALYZE) means the same thing as + * EXPLAIN (ANALYZE ON). + */ + if (!opt->arg) + { + return true; + } + else if (IsA(opt->arg, Integer)) + { + if (intVal(opt->arg) == 0) + return false; + else if (intVal(opt->arg) == 1) + return true; + } + else if (IsA(opt->arg, String)) + { + if (parse_bool(strVal(opt->arg), &res)) + return res; + } + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" requires a Boolean value", + opt->defname))); + + /* silence compiler warning */ + return false; + } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 858e16c..1d51032 100644 *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** static TypeName *TableFuncTypeName(List *** 369,374 **** --- 369,379 ---- %type <defelt> generic_option_elem alter_generic_option_elem %type <list> generic_option_list alter_generic_option_list + %type <str> explain_option_name + %type <node> explain_option_arg + %type <defelt> explain_option_elem + %type <list> explain_option_list + %type <typnam> Typename SimpleTypename ConstTypename GenericType Numeric opt_float Character ConstCharacter *************** opt_name_list: *** 6447,6463 **** * * QUERY: * EXPLAIN [ANALYZE] [VERBOSE] query * *****************************************************************************/ ExplainStmt: EXPLAIN opt_analyze opt_verbose ExplainableStmt { ! ExplainStmt *n = makeNode(ExplainStmt); n->analyze = $2; n->verbose = $3; - n->query = $4; $$ = (Node *)n; } ; ExplainableStmt: --- 6452,6472 ---- * * QUERY: * EXPLAIN [ANALYZE] [VERBOSE] query + * EXPLAIN ( options ) query * *****************************************************************************/ ExplainStmt: EXPLAIN opt_analyze opt_verbose ExplainableStmt { ! ExplainStmt *n = makeExplain(NIL, (Node *) $4); n->analyze = $2; n->verbose = $3; $$ = (Node *)n; } + | EXPLAIN '(' explain_option_list ')' ExplainableStmt + { + $$ = (Node *) makeExplain((List *) $3, (Node *) $5); + } ; ExplainableStmt: *************** ExplainableStmt: *** 6470,6478 **** | ExecuteStmt /* by default all are $$=$1 */ ; opt_analyze: analyze_keyword { $$ = TRUE; } ! | /* EMPTY */ { $$ = FALSE; } ; /***************************************************************************** --- 6479,6529 ---- | ExecuteStmt /* by default all are $$=$1 */ ; + /* + * The precedence declaration for the opt_analyze EMPTY case, below, is + * necessary to prevent a shift/reduce conflict in the second production for + * ExplainStmt, above. Otherwise, when the parser encounters "EXPLAIN (", it + * can't tell whether the "(" is the beginning of a SelectStmt or the beginning + * of the options list. The precedence declaration below forces the latter + * interpretation. + * + * It might seem that we could get away with simply changing the definition of + * ExplainableStmt to use select_without_parens rather than SelectStmt, but + * that does not work, because select_without_parens produces expressions such + * as "(SELECT NULL) ORDER BY 1" that we interpret as legal queries. + */ opt_analyze: analyze_keyword { $$ = TRUE; } ! | /* EMPTY */ %prec UMINUS { $$ = FALSE; } ! ; ! ! explain_option_list: ! explain_option_elem ! { ! $$ = list_make1($1); ! } ! | explain_option_list ',' explain_option_elem ! { ! $$ = lappend($1, $3); ! } ! ; ! ! explain_option_elem: ! explain_option_name explain_option_arg ! { ! $$ = makeDefElem($1, $2); ! } ! ; ! ! explain_option_name: ! ColLabel { $$ = $1; } ! ; ! ! explain_option_arg: ! opt_boolean { $$ = (Node *) makeString($1); } ! | ColId_or_Sconst { $$ = (Node *) makeString($1); } ! | SignedIconst { $$ = (Node *) makeInteger($1); } ! | /* EMPTY */ { $$ = NULL; } ; /***************************************************************************** diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 333cb25..9005209 100644 *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** static RangeTblEntry *find_rte_by_refnam *** 187,193 **** deparse_context *context); static const char *get_simple_binary_op_name(OpExpr *expr); static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags); - static void appendStringInfoSpaces(StringInfo buf, int count); static void appendContextKeyword(deparse_context *context, const char *str, int indentBefore, int indentAfter, int indentPlus); static void get_rule_expr(Node *node, deparse_context *context, --- 187,192 ---- *************** isSimpleNode(Node *node, Node *parentNod *** 4173,4188 **** /* - * appendStringInfoSpaces - append spaces to buffer - */ - static void - appendStringInfoSpaces(StringInfo buf, int count) - { - while (count-- > 0) - appendStringInfoChar(buf, ' '); - } - - /* * appendContextKeyword - append a keyword to buffer * * If prettyPrint is enabled, perform a line break, and adjust indentation. --- 4172,4177 ---- diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index aa3b643..238b654 100644 *** a/src/include/commands/explain.h --- b/src/include/commands/explain.h *************** extern void ExplainOnePlan(PlannedStmt * *** 44,49 **** TupOutputState *tstate); extern void ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc, ! bool analyze, bool verbose); #endif /* EXPLAIN_H */ --- 44,49 ---- TupOutputState *tstate); extern void ExplainPrintPlan(StringInfo str, QueryDesc *queryDesc, ! ExplainStmt *stmt); #endif /* EXPLAIN_H */ diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h index 17990c4..3932107 100644 *** a/src/include/lib/stringinfo.h --- b/src/include/lib/stringinfo.h *************** extern void appendStringInfoChar(StringI *** 132,137 **** --- 132,143 ---- (void)((str)->data[(str)->len] = (ch), (str)->data[++(str)->len] = '\0')) /*------------------------ + * appendStringInfoSpaces + * Append a given number of spaces to str. + */ + extern void appendStringInfoSpaces(StringInfo str, int count); + + /*------------------------ * appendBinaryStringInfo * Append arbitrary binary data to a StringInfo, allocating more space * if necessary. diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 7d24346..4ee826a 100644 *** a/src/include/nodes/makefuncs.h --- b/src/include/nodes/makefuncs.h *************** extern DefElem *makeDefElem(char *name, *** 69,72 **** --- 69,74 ---- extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg, DefElemAction defaction); + extern ExplainStmt *makeExplain(List *options, Node *query); + #endif /* MAKEFUNC_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 9d53ab9..b7319c1 100644 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** typedef struct ExplainStmt *** 2193,2199 **** NodeTag type; Node *query; /* the query (as a raw parse tree) */ bool verbose; /* print plan info */ ! bool analyze; /* get statistics by executing plan */ } ExplainStmt; /* ---------------------- --- 2193,2200 ---- NodeTag type; Node *query; /* the query (as a raw parse tree) */ bool verbose; /* print plan info */ ! bool analyze; /* actually execute plan */ ! bool costs; /* print costs and times */ } ExplainStmt; /* ----------------------
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers