/*------------------------------------------------------------------------- * * explain.c * Explain query execution plans * * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.188 2009/07/26 23:34:17 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/xact.h" #include "catalog/pg_constraint.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/explain.h" #include "commands/prepare.h" #include "commands/trigger.h" #include "executor/instrument.h" #include "optimizer/clauses.h" #include "optimizer/planner.h" #include "optimizer/var.h" #include "parser/parsetree.h" #include "rewrite/rewriteHandler.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/tuplesort.h" #include "utils/snapmgr.h" /* Hook for plugins to get control in ExplainOneQuery() */ ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL; /* Hook for plugins to get control in explain_get_index_name() */ explain_get_index_name_hook_type explain_get_index_name_hook = NULL; static void ExplainOneQuery(Query *query, ExplainState *es, const char *queryString, ParamListInfo params); static void report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf); static double elapsed_time(instr_time *starttime); 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 ExplainSubPlans(List *plans, int indent, ExplainState *es); /* * ExplainQuery - * execute an EXPLAIN command */ void ExplainQuery(ExplainStmt *stmt, const char *queryString, ParamListInfo params, DestReceiver *dest) { ExplainState es; Oid *param_types; int num_params; TupOutputState *tstate; List *rewritten; ListCell *lc; /* Initialize ExplainState. */ ExplainInitState(&es); /* Parse options list. */ foreach(lc, stmt->options) { DefElem *opt = (DefElem *) lfirst(lc); if (strcmp(opt->defname, "analyze") == 0) es.analyze = defGetBoolean(opt); else if (strcmp(opt->defname, "verbose") == 0) es.verbose = defGetBoolean(opt); else if (strcmp(opt->defname, "costs") == 0) es.costs = defGetBoolean(opt); else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unrecognized EXPLAIN option \"%s\"", opt->defname))); } /* Convert parameter type data to the form parser wants */ getParamListTypes(params, ¶m_types, &num_params); /* * Run parse analysis and rewrite. Note this also acquires sufficient * locks on the source table(s). * * Because the parser and planner tend to scribble on their input, we make * a preliminary copy of the source querytree. This prevents problems in * the case that the EXPLAIN is in a portal or plpgsql function and is * executed repeatedly. (See also the same hack in DECLARE CURSOR and * PREPARE.) XXX FIXME someday. */ rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query), queryString, param_types, num_params); if (rewritten == NIL) { /* In the case of an INSTEAD NOTHING, tell at least that */ appendStringInfoString(es.str, "Query rewrites to nothing\n"); } else { ListCell *l; /* Explain every plan */ foreach(l, rewritten) { ExplainOneQuery((Query *) lfirst(l), &es, queryString, params); /* put a blank line between plans */ if (lnext(l) != NULL) appendStringInfoChar(es.str, '\n'); } } /* output tuples */ tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt)); do_text_output_multiline(tstate, es.str->data); end_tup_output(tstate); pfree(es.str->data); } /* * Initialize ExplainState. */ void ExplainInitState(ExplainState *es) { /* Set default options. */ memset(es, 0, sizeof(ExplainState)); es->costs = true; /* Prepare output buffer. */ es->str = makeStringInfo(); } /* * ExplainResultDesc - * construct the result tupledesc for an EXPLAIN */ TupleDesc ExplainResultDesc(ExplainStmt *stmt) { TupleDesc tupdesc; /* need a tuple descriptor representing a single TEXT column */ tupdesc = CreateTemplateTupleDesc(1, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN", TEXTOID, -1, 0); return tupdesc; } /* * ExplainOneQuery - * print out the execution plan for one Query */ static void ExplainOneQuery(Query *query, ExplainState *es, const char *queryString, ParamListInfo params) { /* planner will not cope with utility statements */ if (query->commandType == CMD_UTILITY) { ExplainOneUtility(query->utilityStmt, es, queryString, params); return; } /* if an advisor plugin is present, let it manage things */ if (ExplainOneQuery_hook) (*ExplainOneQuery_hook) (query, es, queryString, params); else { PlannedStmt *plan; /* plan the query */ plan = pg_plan_query(query, 0, params); /* run it (if needed) and produce output */ ExplainOnePlan(plan, es, queryString, params); } } /* * ExplainOneUtility - * print out the execution plan for one utility statement * (In general, utility statements don't have plans, but there are some * we treat as special cases) * * This is exported because it's called back from prepare.c in the * EXPLAIN EXECUTE case */ void ExplainOneUtility(Node *utilityStmt, ExplainState *es, const char *queryString, ParamListInfo params) { if (utilityStmt == NULL) return; if (IsA(utilityStmt, ExecuteStmt)) ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es, queryString, params); else if (IsA(utilityStmt, NotifyStmt)) appendStringInfoString(es->str, "NOTIFY\n"); else appendStringInfoString(es->str, "Utility statements have no plan structure\n"); } /* * ExplainOnePlan - * given a planned query, execute it if needed, and then print * EXPLAIN output * * Since we ignore any DeclareCursorStmt that might be attached to the query, * if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll actually run the * query. This is different from pre-8.3 behavior but seems more useful than * not running the query. No cursor will be created, however. * * This is exported because it's called back from prepare.c in the * EXPLAIN EXECUTE case, and because an index advisor plugin would need * to call it. */ void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, const char *queryString, ParamListInfo params) { QueryDesc *queryDesc; instr_time starttime; double totaltime = 0; int eflags; /* * Use a snapshot with an updated command ID to ensure this query sees * results of any previously executed queries. */ PushUpdatedSnapshot(GetActiveSnapshot()); /* Create a QueryDesc requesting no output */ queryDesc = CreateQueryDesc(plannedstmt, queryString, GetActiveSnapshot(), InvalidSnapshot, None_Receiver, params, es->analyze); INSTR_TIME_SET_CURRENT(starttime); /* If analyzing, we need to cope with queued triggers */ if (es->analyze) AfterTriggerBeginQuery(); /* Select execution options */ if (es->analyze) eflags = 0; /* default run-to-completion flags */ else eflags = EXEC_FLAG_EXPLAIN_ONLY; /* call ExecutorStart to prepare the plan for execution */ ExecutorStart(queryDesc, eflags); /* Execute the plan for statistics if asked for */ if (es->analyze) { /* run the plan */ ExecutorRun(queryDesc, ForwardScanDirection, 0L); /* We can't clean up 'till we're done printing the stats... */ totaltime += elapsed_time(&starttime); } /* Create textual dump of plan tree */ ExplainPrintPlan(es, queryDesc); /* * If we ran the command, run any AFTER triggers it queued. (Note this * will not include DEFERRED triggers; since those don't run until end of * transaction, we can't measure them.) Include into total runtime. */ if (es->analyze) { INSTR_TIME_SET_CURRENT(starttime); AfterTriggerEndQuery(queryDesc->estate); totaltime += elapsed_time(&starttime); } /* Print info about runtime of triggers */ if (es->analyze) { ResultRelInfo *rInfo; bool show_relname; int numrels = queryDesc->estate->es_num_result_relations; List *targrels = queryDesc->estate->es_trig_target_relations; int nr; ListCell *l; show_relname = (numrels > 1 || targrels != NIL); rInfo = queryDesc->estate->es_result_relations; for (nr = 0; nr < numrels; rInfo++, nr++) report_triggers(rInfo, show_relname, es->str); foreach(l, targrels) { rInfo = (ResultRelInfo *) lfirst(l); report_triggers(rInfo, show_relname, es->str); } } /* * Close down the query and free resources. Include time for this in the * total runtime (although it should be pretty minimal). */ INSTR_TIME_SET_CURRENT(starttime); ExecutorEnd(queryDesc); FreeQueryDesc(queryDesc); PopActiveSnapshot(); /* We need a CCI just in case query expanded to multiple plans */ if (es->analyze) CommandCounterIncrement(); totaltime += elapsed_time(&starttime); if (es->analyze) appendStringInfo(es->str, "Total runtime: %.3f ms\n", 1000.0 * totaltime); } /* * ExplainPrintPlan - * convert a QueryDesc's plan tree to text and append it to es->str * * The caller should have set up the options fields of *es, as well as * initializing the output buffer es->str. Other fields in *es are * initialized here. * * NB: will not work on utility statements */ void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc) { Assert(queryDesc->plannedstmt != NULL); es->pstmt = queryDesc->plannedstmt; es->rtable = queryDesc->plannedstmt->rtable; ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate, NULL, 0, es); } /* * report_triggers - * report execution stats for a single relation's triggers */ static void report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf) { int nt; if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument) return; for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++) { Trigger *trig = rInfo->ri_TrigDesc->triggers + nt; Instrumentation *instr = rInfo->ri_TrigInstrument + nt; char *conname; /* Must clean up instrumentation state */ InstrEndLoop(instr); /* * We ignore triggers that were never invoked; they likely aren't * relevant to the current query type. */ if (instr->ntuples == 0) continue; if (OidIsValid(trig->tgconstraint) && (conname = get_constraint_name(trig->tgconstraint)) != NULL) { appendStringInfo(buf, "Trigger for constraint %s", conname); pfree(conname); } else appendStringInfo(buf, "Trigger %s", trig->tgname); if (show_relname) appendStringInfo(buf, " on %s", RelationGetRelationName(rInfo->ri_RelationDesc)); appendStringInfo(buf, ": time=%.3f calls=%.0f\n", 1000.0 * instr->total, instr->ntuples); } } /* Compute elapsed time in seconds since given timestamp */ static double elapsed_time(instr_time *starttime) { instr_time endtime; INSTR_TIME_SET_CURRENT(endtime); INSTR_TIME_SUBTRACT(endtime, *starttime); return INSTR_TIME_GET_DOUBLE(endtime); } /* * ExplainNode - * converts a Plan node into ascii string and appends it to es->str * * planstate points to the executor state node corresponding to the plan node. * We need this to get at the instrumentation data (if any) as well as the * list of subplans. * * outer_plan, if not null, references another plan node that is the outer * side of a join with the current node. This is only interesting for * deciphering runtime keys of an inner indexscan. * * If indent is positive, we indent the plan output accordingly and put "->" * in front of it. This should only happen for child plan nodes. */ 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; } switch (nodeTag(plan)) { case T_Result: pname = "Result"; break; case T_Append: pname = "Append"; break; case T_RecursiveUnion: pname = "Recursive Union"; break; case T_BitmapAnd: pname = "BitmapAnd"; break; case T_BitmapOr: pname = "BitmapOr"; break; case T_NestLoop: switch (((NestLoop *) plan)->join.jointype) { case JOIN_INNER: pname = "Nested Loop"; break; case JOIN_LEFT: pname = "Nested Loop Left Join"; break; case JOIN_FULL: pname = "Nested Loop Full Join"; break; case JOIN_RIGHT: pname = "Nested Loop Right Join"; break; case JOIN_SEMI: pname = "Nested Loop Semi Join"; break; case JOIN_ANTI: pname = "Nested Loop Anti Join"; break; default: pname = "Nested Loop ??? Join"; break; } break; case T_MergeJoin: switch (((MergeJoin *) plan)->join.jointype) { case JOIN_INNER: pname = "Merge Join"; break; case JOIN_LEFT: pname = "Merge Left Join"; break; case JOIN_FULL: pname = "Merge Full Join"; break; case JOIN_RIGHT: pname = "Merge Right Join"; break; case JOIN_SEMI: pname = "Merge Semi Join"; break; case JOIN_ANTI: pname = "Merge Anti Join"; break; default: pname = "Merge ??? Join"; break; } break; case T_HashJoin: switch (((HashJoin *) plan)->join.jointype) { case JOIN_INNER: pname = "Hash Join"; break; case JOIN_LEFT: pname = "Hash Left Join"; break; case JOIN_FULL: pname = "Hash Full Join"; break; case JOIN_RIGHT: pname = "Hash Right Join"; break; case JOIN_SEMI: pname = "Hash Semi Join"; break; case JOIN_ANTI: pname = "Hash Anti Join"; break; default: pname = "Hash ??? Join"; break; } break; case T_SeqScan: pname = "Seq Scan"; break; case T_IndexScan: pname = "Index Scan"; break; case T_BitmapIndexScan: pname = "Bitmap Index Scan"; break; case T_BitmapHeapScan: pname = "Bitmap Heap Scan"; break; case T_TidScan: pname = "Tid Scan"; break; case T_SubqueryScan: pname = "Subquery Scan"; break; case T_FunctionScan: pname = "Function Scan"; break; case T_ValuesScan: pname = "Values Scan"; break; case T_CteScan: pname = "CTE Scan"; break; case T_WorkTableScan: pname = "WorkTable Scan"; break; case T_Material: pname = "Materialize"; break; case T_Sort: pname = "Sort"; break; case T_Group: pname = "Group"; break; case T_Agg: switch (((Agg *) plan)->aggstrategy) { case AGG_PLAIN: pname = "Aggregate"; break; case AGG_SORTED: pname = "GroupAggregate"; break; case AGG_HASHED: pname = "HashAggregate"; break; default: pname = "Aggregate ???"; break; } break; case T_WindowAgg: pname = "WindowAgg"; break; case T_Unique: pname = "Unique"; break; case T_SetOp: switch (((SetOp *) plan)->strategy) { case SETOP_SORTED: switch (((SetOp *) plan)->cmd) { case SETOPCMD_INTERSECT: pname = "SetOp Intersect"; break; case SETOPCMD_INTERSECT_ALL: pname = "SetOp Intersect All"; break; case SETOPCMD_EXCEPT: pname = "SetOp Except"; break; case SETOPCMD_EXCEPT_ALL: pname = "SetOp Except All"; break; default: pname = "SetOp ???"; break; } break; case SETOP_HASHED: switch (((SetOp *) plan)->cmd) { case SETOPCMD_INTERSECT: pname = "HashSetOp Intersect"; break; case SETOPCMD_INTERSECT_ALL: pname = "HashSetOp Intersect All"; break; case SETOPCMD_EXCEPT: pname = "HashSetOp Except"; break; case SETOPCMD_EXCEPT_ALL: pname = "HashSetOp Except All"; break; default: pname = "HashSetOp ???"; break; } break; default: pname = "SetOp ???"; break; } break; case T_Limit: pname = "Limit"; break; case T_Hash: pname = "Hash"; break; default: pname = "???"; 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->costs) appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)", plan->startup_cost, plan->total_cost, plan->plan_rows, plan->plan_width); /* * We have to forcibly clean up the instrumentation state because we * haven't done ExecutorEnd yet. This is pretty grotty ... */ if (planstate->instrument) InstrEndLoop(planstate->instrument); if (planstate->instrument && planstate->instrument->nloops > 0) { double nloops = planstate->instrument->nloops; appendStringInfo(es->str, " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)", 1000.0 * planstate->instrument->startup / nloops, 1000.0 * planstate->instrument->total / nloops, planstate->instrument->ntuples / nloops, planstate->instrument->nloops); } else if (es->analyze) appendStringInfoString(es->str, " (never executed)"); appendStringInfoChar(es->str, '\n'); /* target list */ if (es->verbose) 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: 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: { /* * The tidquals list has OR semantics, so be sure to show it * as an OR condition. */ List *tidquals = ((TidScan *) plan)->tidquals; 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; } /* initPlan-s */ if (plan->initPlan) ExplainSubPlans(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); } /* special child plans */ 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; case T_SubqueryScan: { SubqueryScan *subqueryscan = (SubqueryScan *) plan; SubqueryScanState *subquerystate = (SubqueryScanState *) planstate; ExplainNode(subqueryscan->subplan, subquerystate->subplan, NULL, indent + 3, es); } break; default: break; } /* subPlan-s */ if (planstate->subPlan) ExplainSubPlans(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; ListCell *lc; int i; /* No work if empty tlist (this occurs eg in bitmap indexscans) */ if (plan->targetlist == NIL) return; /* The tlist of an Append isn't real helpful, so suppress it */ if (IsA(plan, Append)) return; /* Likewise for RecursiveUnion */ if (IsA(plan, RecursiveUnion)) return; /* Set up deparsing context */ context = deparse_context_for_plan((Node *) plan, NULL, es->rtable, es->pstmt->subplans); useprefix = list_length(es->rtable) > 1; /* Emit line prefix */ appendStringInfoSpaces(es->str, indent * 2); appendStringInfoString(es->str, " Output: "); /* Deparse each non-junk result column */ i = 0; foreach(lc, plan->targetlist) { TargetEntry *tle = (TargetEntry *) lfirst(lc); if (tle->resjunk) continue; if (i++ > 0) appendStringInfoString(es->str, ", "); appendStringInfoString(es->str, deparse_expression((Node *) tle->expr, context, useprefix, false)); } appendStringInfoChar(es->str, '\n'); } /* * 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) return; /* Convert AND list to explicit AND */ 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 es->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; useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan)); show_qual(qual, qlabel, scan_plan, outer_plan, indent, useprefix, es); } /* * Show a qualifier expression for an upper-level plan node */ static void show_upper_qual(List *qual, const char *qlabel, Plan *plan, int indent, ExplainState *es) { bool useprefix; 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) { int nkeys = ((Sort *) sortplan)->numCols; AttrNumber *keycols = ((Sort *) sortplan)->sortColIdx; List *context; bool useprefix; int keyno; char *exprstr; 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, NULL, es->rtable, es->pstmt->subplans); useprefix = list_length(es->rtable) > 1; for (keyno = 0; keyno < nkeys; keyno++) { /* find key expression in tlist */ AttrNumber keyresno = keycols[keyno]; TargetEntry *target = get_tle_by_resno(sortplan->targetlist, keyresno); if (!target) elog(ERROR, "no tlist entry for key %d", keyresno); /* Deparse the expression, showing any top-level cast */ exprstr = deparse_expression((Node *) target->expr, context, useprefix, true); /* And add to es->str */ if (keyno > 0) appendStringInfoString(es->str, ", "); appendStringInfoString(es->str, exprstr); } appendStringInfoChar(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->analyze && sortstate->sort_Done && sortstate->tuplesortstate != NULL) { char *sortinfo; sortinfo = tuplesort_explain((Tuplesortstate *) sortstate->tuplesortstate); appendStringInfoSpaces(es->str, indent * 2); appendStringInfo(es->str, " %s\n", sortinfo); pfree(sortinfo); } } /* * Fetch the name of an index in an EXPLAIN * * We allow plugins to get control here so that plans involving hypothetical * indexes can be explained. */ static const char * explain_get_index_name(Oid indexId) { const char *result; if (explain_get_index_name_hook) result = (*explain_get_index_name_hook) (indexId); else result = NULL; if (result == NULL) { /* default behavior: look in the catalogs and quote it */ result = get_rel_name(indexId); if (result == NULL) elog(ERROR, "cache lookup failed for index %u", indexId); result = quote_identifier(result); } return result; } /* * Show the target of a Scan node */ static void ExplainScanTarget(Scan *plan, ExplainState *es) { char *objectname = NULL; RangeTblEntry *rte; if (plan->scanrelid <= 0) /* Is this still possible? */ return; rte = rt_fetch(plan->scanrelid, es->rtable); switch (nodeTag(plan)) { case T_SeqScan: case T_IndexScan: 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: { Node *funcexpr; /* 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 the constituent plans of an Append, BitmapAnd, or BitmapOr node. * * Ordinarily we don't pass down outer_plan to our child nodes, but in these * cases we must, since the node could be an "inner indexscan" in which case * outer references can appear in the child nodes. */ static void ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan, int indent, ExplainState *es) { ListCell *lst; int j = 0; foreach(lst, plans) { Plan *subnode = (Plan *) lfirst(lst); ExplainNode(subnode, planstate[j], outer_plan, indent + 3, es); j++; } } /* * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes). */ static void ExplainSubPlans(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); } }