1 /*-------------------------------------------------------------------------
4 * Explain query execution plans
6 * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994-5, Regents of the University of California
10 * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.160 2007/03/13 00:33:39 tgl Exp $
12 *-------------------------------------------------------------------------
16 #include "access/xact.h"
17 #include "catalog/pg_constraint.h"
18 #include "catalog/pg_type.h"
19 #include "commands/explain.h"
20 #include "commands/prepare.h"
21 #include "commands/trigger.h"
22 #include "executor/instrument.h"
23 #include "nodes/print.h"
24 #include "optimizer/clauses.h"
25 #include "optimizer/planner.h"
26 #include "optimizer/var.h"
27 #include "parser/parsetree.h"
28 #include "rewrite/rewriteHandler.h"
29 #include "tcop/tcopprot.h"
30 #include "utils/builtins.h"
31 #include "utils/guc.h"
32 #include "utils/lsyscache.h"
35 typedef struct ExplainState
38 bool printNodes; /* do nodeToString() too */
39 bool printAnalyze; /* print actual times */
41 PlannedStmt *pstmt; /* top of plan */
42 List *rtable; /* range table */
45 static void ExplainOneQuery(Query *query, bool isCursor, int cursorOptions,
46 ExplainStmt *stmt, const char *queryString,
47 ParamListInfo params, TupOutputState *tstate);
48 static double elapsed_time(instr_time *starttime);
49 static void explain_outNode(StringInfo str,
50 Plan *plan, PlanState *planstate,
52 int indent, ExplainState *es);
53 static void show_scan_qual(List *qual, const char *qlabel,
54 int scanrelid, Plan *outer_plan, Plan *inner_plan,
55 StringInfo str, int indent, ExplainState *es);
56 static void show_upper_qual(List *qual, const char *qlabel, Plan *plan,
57 StringInfo str, int indent, ExplainState *es);
58 static void show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
60 StringInfo str, int indent, ExplainState *es);
64 * execute an EXPLAIN command
67 ExplainQuery(ExplainStmt *stmt, const char *queryString,
68 ParamListInfo params, DestReceiver *dest)
72 TupOutputState *tstate;
76 /* Convert parameter type data to the form parser wants */
77 getParamListTypes(params, ¶m_types, &num_params);
80 * Run parse analysis and rewrite. Note this also acquires sufficient
81 * locks on the source table(s).
83 * Because the parser and planner tend to scribble on their input, we
84 * make a preliminary copy of the source querytree. This prevents
85 * problems in the case that the EXPLAIN is in a portal or plpgsql
86 * function and is executed repeatedly. (See also the same hack in
87 * DECLARE CURSOR and PREPARE.) XXX FIXME someday.
89 rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
90 queryString, param_types, num_params);
92 /* prepare for projection of tuples */
93 tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
97 /* In the case of an INSTEAD NOTHING, tell at least that */
98 do_text_output_oneline(tstate, "Query rewrites to nothing");
102 /* Explain every plan */
103 foreach(l, rewritten)
105 ExplainOneQuery((Query *) lfirst(l), false, 0,
106 stmt, queryString, params, tstate);
107 /* put a blank line between plans */
108 if (lnext(l) != NULL)
109 do_text_output_oneline(tstate, "");
113 end_tup_output(tstate);
117 * ExplainResultDesc -
118 * construct the result tupledesc for an EXPLAIN
121 ExplainResultDesc(ExplainStmt *stmt)
125 /* need a tuple descriptor representing a single TEXT column */
126 tupdesc = CreateTemplateTupleDesc(1, false);
127 TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
134 * print out the execution plan for one Query
137 ExplainOneQuery(Query *query, bool isCursor, int cursorOptions,
138 ExplainStmt *stmt, const char *queryString,
139 ParamListInfo params, TupOutputState *tstate)
142 QueryDesc *queryDesc;
144 /* planner will not cope with utility statements */
145 if (query->commandType == CMD_UTILITY)
147 ExplainOneUtility(query->utilityStmt, stmt,
148 queryString, params, tstate);
153 plan = planner(query, isCursor, cursorOptions, params);
156 * Update snapshot command ID to ensure this query sees results of any
157 * previously executed queries. (It's a bit cheesy to modify
158 * ActiveSnapshot without making a copy, but for the limited ways in which
159 * EXPLAIN can be invoked, I think it's OK, because the active snapshot
160 * shouldn't be shared with anything else anyway.)
162 ActiveSnapshot->curcid = GetCurrentCommandId();
164 /* Create a QueryDesc requesting no output */
165 queryDesc = CreateQueryDesc(plan,
166 ActiveSnapshot, InvalidSnapshot,
167 None_Receiver, params,
170 ExplainOnePlan(queryDesc, stmt, tstate);
174 * ExplainOneUtility -
175 * print out the execution plan for one utility statement
176 * (In general, utility statements don't have plans, but there are some
177 * we treat as special cases)
179 * This is exported because it's called back from prepare.c in the
180 * EXPLAIN EXECUTE case
183 ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
184 const char *queryString, ParamListInfo params,
185 TupOutputState *tstate)
187 if (utilityStmt == NULL)
190 if (IsA(utilityStmt, DeclareCursorStmt))
192 DeclareCursorStmt *dcstmt = (DeclareCursorStmt *) utilityStmt;
199 /* Convert parameter type data to the form parser wants */
200 getParamListTypes(params, ¶m_types, &num_params);
203 * Run parse analysis and rewrite. Note this also acquires sufficient
204 * locks on the source table(s).
206 * Because the parser and planner tend to scribble on their input, we
207 * make a preliminary copy of the source querytree. This prevents
208 * problems in the case that the DECLARE CURSOR is in a portal or
209 * plpgsql function and is executed repeatedly. (See also the same
210 * hack in COPY and PREPARE.) XXX FIXME someday.
212 rewritten = pg_analyze_and_rewrite((Node *) copyObject(dcstmt->query),
214 param_types, num_params);
216 /* We don't expect more or less than one result query */
217 if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
218 elog(ERROR, "unexpected rewrite result");
219 query = (Query *) linitial(rewritten);
220 if (query->commandType != CMD_SELECT)
221 elog(ERROR, "unexpected rewrite result");
223 /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
226 (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
227 errmsg("DECLARE CURSOR cannot specify INTO")));
229 /* do not actually execute the underlying query! */
230 memcpy(&newstmt, stmt, sizeof(ExplainStmt));
231 newstmt.analyze = false;
232 ExplainOneQuery(query, true, dcstmt->options, &newstmt,
233 queryString, params, tstate);
235 else if (IsA(utilityStmt, ExecuteStmt))
236 ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
237 queryString, params, tstate);
238 else if (IsA(utilityStmt, NotifyStmt))
239 do_text_output_oneline(tstate, "NOTIFY");
241 do_text_output_oneline(tstate,
242 "Utility statements have no plan structure");
247 * given a planned query, execute it if needed, and then print
250 * This is exported because it's called back from prepare.c in the
251 * EXPLAIN EXECUTE case
253 * Note: the passed-in QueryDesc is freed when we're done with it
256 ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
257 TupOutputState *tstate)
259 instr_time starttime;
260 double totaltime = 0;
265 INSTR_TIME_SET_CURRENT(starttime);
267 /* If analyzing, we need to cope with queued triggers */
269 AfterTriggerBeginQuery();
271 /* Select execution options */
273 eflags = 0; /* default run-to-completion flags */
275 eflags = EXEC_FLAG_EXPLAIN_ONLY;
277 /* call ExecutorStart to prepare the plan for execution */
278 ExecutorStart(queryDesc, eflags);
280 /* Execute the plan for statistics if asked for */
284 ExecutorRun(queryDesc, ForwardScanDirection, 0L);
286 /* We can't clean up 'till we're done printing the stats... */
287 totaltime += elapsed_time(&starttime);
290 es = (ExplainState *) palloc0(sizeof(ExplainState));
292 es->printNodes = stmt->verbose;
293 es->printAnalyze = stmt->analyze;
294 es->pstmt = queryDesc->plannedstmt;
295 es->rtable = queryDesc->plannedstmt->rtable;
302 s = nodeToString(queryDesc->plannedstmt->planTree);
305 if (Explain_pretty_print)
306 f = pretty_format_node_dump(s);
308 f = format_node_dump(s);
310 do_text_output_multiline(tstate, f);
312 do_text_output_oneline(tstate, ""); /* separator line */
316 initStringInfo(&buf);
317 explain_outNode(&buf,
318 queryDesc->plannedstmt->planTree, queryDesc->planstate,
322 * If we ran the command, run any AFTER triggers it queued. (Note this
323 * will not include DEFERRED triggers; since those don't run until end of
324 * transaction, we can't measure them.) Include into total runtime.
328 INSTR_TIME_SET_CURRENT(starttime);
329 AfterTriggerEndQuery(queryDesc->estate);
330 totaltime += elapsed_time(&starttime);
333 /* Print info about runtime of triggers */
334 if (es->printAnalyze)
336 ResultRelInfo *rInfo;
337 int numrels = queryDesc->estate->es_num_result_relations;
340 rInfo = queryDesc->estate->es_result_relations;
341 for (nr = 0; nr < numrels; rInfo++, nr++)
345 if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
347 for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
349 Trigger *trig = rInfo->ri_TrigDesc->triggers + nt;
350 Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
353 /* Must clean up instrumentation state */
357 * We ignore triggers that were never invoked; they likely
358 * aren't relevant to the current query type.
360 if (instr->ntuples == 0)
363 if (OidIsValid(trig->tgconstraint) &&
364 (conname = get_constraint_name(trig->tgconstraint)) != NULL)
366 appendStringInfo(&buf, "Trigger for constraint %s",
371 appendStringInfo(&buf, "Trigger %s", trig->tgname);
374 appendStringInfo(&buf, " on %s",
375 RelationGetRelationName(rInfo->ri_RelationDesc));
377 appendStringInfo(&buf, ": time=%.3f calls=%.0f\n",
378 1000.0 * instr->total,
385 * Close down the query and free resources. Include time for this in the
386 * total runtime (although it should be pretty minimal).
388 INSTR_TIME_SET_CURRENT(starttime);
390 ExecutorEnd(queryDesc);
392 FreeQueryDesc(queryDesc);
394 /* We need a CCI just in case query expanded to multiple plans */
396 CommandCounterIncrement();
398 totaltime += elapsed_time(&starttime);
401 appendStringInfo(&buf, "Total runtime: %.3f ms\n",
403 do_text_output_multiline(tstate, buf.data);
409 /* Compute elapsed time in seconds since given timestamp */
411 elapsed_time(instr_time *starttime)
415 INSTR_TIME_SET_CURRENT(endtime);
418 endtime.tv_sec -= starttime->tv_sec;
419 endtime.tv_usec -= starttime->tv_usec;
420 while (endtime.tv_usec < 0)
422 endtime.tv_usec += 1000000;
426 endtime.QuadPart -= starttime->QuadPart;
429 return INSTR_TIME_GET_DOUBLE(endtime);
434 * converts a Plan node into ascii string and appends it to 'str'
436 * planstate points to the executor state node corresponding to the plan node.
437 * We need this to get at the instrumentation data (if any) as well as the
440 * outer_plan, if not null, references another plan node that is the outer
441 * side of a join with the current node. This is only interesting for
442 * deciphering runtime keys of an inner indexscan.
445 explain_outNode(StringInfo str,
446 Plan *plan, PlanState *planstate,
448 int indent, ExplainState *es)
455 appendStringInfoChar(str, '\n');
459 switch (nodeTag(plan))
474 switch (((NestLoop *) plan)->join.jointype)
477 pname = "Nested Loop";
480 pname = "Nested Loop Left Join";
483 pname = "Nested Loop Full Join";
486 pname = "Nested Loop Right Join";
489 pname = "Nested Loop IN Join";
492 pname = "Nested Loop ??? Join";
497 switch (((MergeJoin *) plan)->join.jointype)
500 pname = "Merge Join";
503 pname = "Merge Left Join";
506 pname = "Merge Full Join";
509 pname = "Merge Right Join";
512 pname = "Merge IN Join";
515 pname = "Merge ??? Join";
520 switch (((HashJoin *) plan)->join.jointype)
526 pname = "Hash Left Join";
529 pname = "Hash Full Join";
532 pname = "Hash Right Join";
535 pname = "Hash IN Join";
538 pname = "Hash ??? Join";
546 pname = "Index Scan";
548 case T_BitmapIndexScan:
549 pname = "Bitmap Index Scan";
551 case T_BitmapHeapScan:
552 pname = "Bitmap Heap Scan";
558 pname = "Subquery Scan";
561 pname = "Function Scan";
564 pname = "Values Scan";
567 pname = "Materialize";
576 switch (((Agg *) plan)->aggstrategy)
582 pname = "GroupAggregate";
585 pname = "HashAggregate";
588 pname = "Aggregate ???";
596 switch (((SetOp *) plan)->cmd)
598 case SETOPCMD_INTERSECT:
599 pname = "SetOp Intersect";
601 case SETOPCMD_INTERSECT_ALL:
602 pname = "SetOp Intersect All";
604 case SETOPCMD_EXCEPT:
605 pname = "SetOp Except";
607 case SETOPCMD_EXCEPT_ALL:
608 pname = "SetOp Except All";
626 appendStringInfoString(str, pname);
627 switch (nodeTag(plan))
630 if (ScanDirectionIsBackward(((IndexScan *) plan)->indexorderdir))
631 appendStringInfoString(str, " Backward");
632 appendStringInfo(str, " using %s",
633 quote_identifier(get_rel_name(((IndexScan *) plan)->indexid)));
636 case T_BitmapHeapScan:
638 if (((Scan *) plan)->scanrelid > 0)
640 RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
644 /* Assume it's on a real relation */
645 Assert(rte->rtekind == RTE_RELATION);
647 /* We only show the rel name, not schema name */
648 relname = get_rel_name(rte->relid);
650 appendStringInfo(str, " on %s",
651 quote_identifier(relname));
652 if (strcmp(rte->eref->aliasname, relname) != 0)
653 appendStringInfo(str, " %s",
654 quote_identifier(rte->eref->aliasname));
657 case T_BitmapIndexScan:
658 appendStringInfo(str, " on %s",
659 quote_identifier(get_rel_name(((BitmapIndexScan *) plan)->indexid)));
662 if (((Scan *) plan)->scanrelid > 0)
664 RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
667 appendStringInfo(str, " %s",
668 quote_identifier(rte->eref->aliasname));
672 if (((Scan *) plan)->scanrelid > 0)
674 RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
679 /* Assert it's on a RangeFunction */
680 Assert(rte->rtekind == RTE_FUNCTION);
683 * If the expression is still a function call, we can get the
684 * real name of the function. Otherwise, punt (this can
685 * happen if the optimizer simplified away the function call,
688 funcexpr = ((FunctionScan *) plan)->funcexpr;
689 if (funcexpr && IsA(funcexpr, FuncExpr))
691 Oid funcid = ((FuncExpr *) funcexpr)->funcid;
693 /* We only show the func name, not schema name */
694 proname = get_func_name(funcid);
697 proname = rte->eref->aliasname;
699 appendStringInfo(str, " on %s",
700 quote_identifier(proname));
701 if (strcmp(rte->eref->aliasname, proname) != 0)
702 appendStringInfo(str, " %s",
703 quote_identifier(rte->eref->aliasname));
707 if (((Scan *) plan)->scanrelid > 0)
709 RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
713 /* Assert it's on a values rte */
714 Assert(rte->rtekind == RTE_VALUES);
716 valsname = rte->eref->aliasname;
718 appendStringInfo(str, " on %s",
719 quote_identifier(valsname));
726 appendStringInfo(str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
727 plan->startup_cost, plan->total_cost,
728 plan->plan_rows, plan->plan_width);
731 * We have to forcibly clean up the instrumentation state because we
732 * haven't done ExecutorEnd yet. This is pretty grotty ...
734 if (planstate->instrument)
735 InstrEndLoop(planstate->instrument);
737 if (planstate->instrument && planstate->instrument->nloops > 0)
739 double nloops = planstate->instrument->nloops;
741 appendStringInfo(str, " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
742 1000.0 * planstate->instrument->startup / nloops,
743 1000.0 * planstate->instrument->total / nloops,
744 planstate->instrument->ntuples / nloops,
745 planstate->instrument->nloops);
747 else if (es->printAnalyze)
748 appendStringInfo(str, " (never executed)");
749 appendStringInfoChar(str, '\n');
751 /* quals, sort keys, etc */
752 switch (nodeTag(plan))
755 show_scan_qual(((IndexScan *) plan)->indexqualorig,
757 ((Scan *) plan)->scanrelid,
760 show_scan_qual(plan->qual,
762 ((Scan *) plan)->scanrelid,
766 case T_BitmapIndexScan:
767 show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
769 ((Scan *) plan)->scanrelid,
773 case T_BitmapHeapScan:
774 /* XXX do we want to show this in production? */
775 show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
777 ((Scan *) plan)->scanrelid,
784 show_scan_qual(plan->qual,
786 ((Scan *) plan)->scanrelid,
791 show_scan_qual(plan->qual,
793 ((Scan *) plan)->scanrelid,
795 ((SubqueryScan *) plan)->subplan,
801 * The tidquals list has OR semantics, so be sure to show it
802 * as an OR condition.
804 List *tidquals = ((TidScan *) plan)->tidquals;
806 if (list_length(tidquals) > 1)
807 tidquals = list_make1(make_orclause(tidquals));
808 show_scan_qual(tidquals,
810 ((Scan *) plan)->scanrelid,
813 show_scan_qual(plan->qual,
815 ((Scan *) plan)->scanrelid,
821 show_upper_qual(((NestLoop *) plan)->join.joinqual,
824 show_upper_qual(plan->qual,
829 show_upper_qual(((MergeJoin *) plan)->mergeclauses,
832 show_upper_qual(((MergeJoin *) plan)->join.joinqual,
835 show_upper_qual(plan->qual,
840 show_upper_qual(((HashJoin *) plan)->hashclauses,
843 show_upper_qual(((HashJoin *) plan)->join.joinqual,
846 show_upper_qual(plan->qual,
852 show_upper_qual(plan->qual,
858 ((Sort *) plan)->numCols,
859 ((Sort *) plan)->sortColIdx,
864 show_upper_qual((List *) ((Result *) plan)->resconstantqual,
865 "One-Time Filter", plan,
867 show_upper_qual(plan->qual,
880 for (i = 0; i < indent; i++)
881 appendStringInfo(str, " ");
882 appendStringInfo(str, " InitPlan\n");
883 foreach(lst, planstate->initPlan)
885 SubPlanState *sps = (SubPlanState *) lfirst(lst);
886 SubPlan *sp = (SubPlan *) sps->xprstate.expr;
888 for (i = 0; i < indent; i++)
889 appendStringInfo(str, " ");
890 appendStringInfo(str, " -> ");
892 exec_subplan_get_plan(es->pstmt, sp),
902 for (i = 0; i < indent; i++)
903 appendStringInfo(str, " ");
904 appendStringInfo(str, " -> ");
907 * Ordinarily we don't pass down our own outer_plan value to our child
908 * nodes, but in bitmap scan trees we must, since the bottom
909 * BitmapIndexScan nodes may have outer references.
911 explain_outNode(str, outerPlan(plan),
912 outerPlanState(planstate),
913 IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
920 for (i = 0; i < indent; i++)
921 appendStringInfo(str, " ");
922 appendStringInfo(str, " -> ");
923 explain_outNode(str, innerPlan(plan),
924 innerPlanState(planstate),
929 if (IsA(plan, Append))
931 Append *appendplan = (Append *) plan;
932 AppendState *appendstate = (AppendState *) planstate;
937 foreach(lst, appendplan->appendplans)
939 Plan *subnode = (Plan *) lfirst(lst);
941 for (i = 0; i < indent; i++)
942 appendStringInfo(str, " ");
943 appendStringInfo(str, " -> ");
946 * Ordinarily we don't pass down our own outer_plan value to our
947 * child nodes, but in an Append we must, since we might be
948 * looking at an appendrel indexscan with outer references from
951 explain_outNode(str, subnode,
952 appendstate->appendplans[j],
959 if (IsA(plan, BitmapAnd))
961 BitmapAnd *bitmapandplan = (BitmapAnd *) plan;
962 BitmapAndState *bitmapandstate = (BitmapAndState *) planstate;
967 foreach(lst, bitmapandplan->bitmapplans)
969 Plan *subnode = (Plan *) lfirst(lst);
971 for (i = 0; i < indent; i++)
972 appendStringInfo(str, " ");
973 appendStringInfo(str, " -> ");
975 explain_outNode(str, subnode,
976 bitmapandstate->bitmapplans[j],
977 outer_plan, /* pass down same outer plan */
983 if (IsA(plan, BitmapOr))
985 BitmapOr *bitmaporplan = (BitmapOr *) plan;
986 BitmapOrState *bitmaporstate = (BitmapOrState *) planstate;
991 foreach(lst, bitmaporplan->bitmapplans)
993 Plan *subnode = (Plan *) lfirst(lst);
995 for (i = 0; i < indent; i++)
996 appendStringInfo(str, " ");
997 appendStringInfo(str, " -> ");
999 explain_outNode(str, subnode,
1000 bitmaporstate->bitmapplans[j],
1001 outer_plan, /* pass down same outer plan */
1007 if (IsA(plan, SubqueryScan))
1009 SubqueryScan *subqueryscan = (SubqueryScan *) plan;
1010 SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
1011 Plan *subnode = subqueryscan->subplan;
1013 for (i = 0; i < indent; i++)
1014 appendStringInfo(str, " ");
1015 appendStringInfo(str, " -> ");
1017 explain_outNode(str, subnode,
1018 subquerystate->subplan,
1024 if (planstate->subPlan)
1028 for (i = 0; i < indent; i++)
1029 appendStringInfo(str, " ");
1030 appendStringInfo(str, " SubPlan\n");
1031 foreach(lst, planstate->subPlan)
1033 SubPlanState *sps = (SubPlanState *) lfirst(lst);
1034 SubPlan *sp = (SubPlan *) sps->xprstate.expr;
1036 for (i = 0; i < indent; i++)
1037 appendStringInfo(str, " ");
1038 appendStringInfo(str, " -> ");
1039 explain_outNode(str,
1040 exec_subplan_get_plan(es->pstmt, sp),
1049 * Show a qualifier expression for a scan plan node
1051 * Note: outer_plan is the referent for any OUTER vars in the scan qual;
1052 * this would be the outer side of a nestloop plan. inner_plan should be
1053 * NULL except for a SubqueryScan plan node, where it should be the subplan.
1056 show_scan_qual(List *qual, const char *qlabel,
1057 int scanrelid, Plan *outer_plan, Plan *inner_plan,
1058 StringInfo str, int indent, ExplainState *es)
1066 /* No work if empty qual */
1070 /* Convert AND list to explicit AND */
1071 node = (Node *) make_ands_explicit(qual);
1073 /* Set up deparsing context */
1074 context = deparse_context_for_plan((Node *) outer_plan,
1075 (Node *) inner_plan,
1077 useprefix = (outer_plan != NULL || inner_plan != NULL);
1079 /* Deparse the expression */
1080 exprstr = deparse_expression(node, context, useprefix, false);
1082 /* And add to str */
1083 for (i = 0; i < indent; i++)
1084 appendStringInfo(str, " ");
1085 appendStringInfo(str, " %s: %s\n", qlabel, exprstr);
1089 * Show a qualifier expression for an upper-level plan node
1092 show_upper_qual(List *qual, const char *qlabel, Plan *plan,
1093 StringInfo str, int indent, ExplainState *es)
1101 /* No work if empty qual */
1105 /* Set up deparsing context */
1106 context = deparse_context_for_plan((Node *) outerPlan(plan),
1107 (Node *) innerPlan(plan),
1109 useprefix = list_length(es->rtable) > 1;
1111 /* Deparse the expression */
1112 node = (Node *) make_ands_explicit(qual);
1113 exprstr = deparse_expression(node, context, useprefix, false);
1115 /* And add to str */
1116 for (i = 0; i < indent; i++)
1117 appendStringInfo(str, " ");
1118 appendStringInfo(str, " %s: %s\n", qlabel, exprstr);
1122 * Show the sort keys for a Sort node.
1125 show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
1127 StringInfo str, int indent, ExplainState *es)
1138 for (i = 0; i < indent; i++)
1139 appendStringInfo(str, " ");
1140 appendStringInfo(str, " %s: ", qlabel);
1142 /* Set up deparsing context */
1143 context = deparse_context_for_plan((Node *) outerPlan(sortplan),
1144 NULL, /* Sort has no innerPlan */
1146 useprefix = list_length(es->rtable) > 1;
1148 for (keyno = 0; keyno < nkeys; keyno++)
1150 /* find key expression in tlist */
1151 AttrNumber keyresno = keycols[keyno];
1152 TargetEntry *target = get_tle_by_resno(sortplan->targetlist, keyresno);
1155 elog(ERROR, "no tlist entry for key %d", keyresno);
1156 /* Deparse the expression, showing any top-level cast */
1157 exprstr = deparse_expression((Node *) target->expr, context,
1159 /* And add to str */
1161 appendStringInfo(str, ", ");
1162 appendStringInfoString(str, exprstr);
1165 appendStringInfo(str, "\n");