Logo Search packages:      
Sourcecode: postgresql-8.4 version File versions  Download package

explain.c

/*-------------------------------------------------------------------------
 *
 * 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.186 2009/06/11 14:48:55 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/xact.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_type.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;


00043 typedef struct ExplainState
{
      /* options */
      bool        printTList;       /* print plan targetlists */
      bool        printAnalyze;     /* print actual times */
      /* other states */
      PlannedStmt *pstmt;                 /* top of plan */
      List     *rtable;             /* range table */
} ExplainState;

static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
                        const char *queryString,
                        ParamListInfo params, TupOutputState *tstate);
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);


/*
 * ExplainQuery -
 *      execute an EXPLAIN command
 */
void
ExplainQuery(ExplainStmt *stmt, const char *queryString,
                   ParamListInfo params, DestReceiver *dest)
{
      Oid            *param_types;
      int               num_params;
      TupOutputState *tstate;
      List     *rewritten;
      ListCell   *l;

      /* Convert parameter type data to the form parser wants */
      getParamListTypes(params, &param_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);

      /* prepare for projection of tuples */
      tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));

      if (rewritten == NIL)
      {
            /* In the case of an INSTEAD NOTHING, tell at least that */
            do_text_output_oneline(tstate, "Query rewrites to nothing");
      }
      else
      {
            /* Explain every plan */
            foreach(l, rewritten)
            {
                  ExplainOneQuery((Query *) lfirst(l), stmt,
                                          queryString, params, tstate);
                  /* put a blank line between plans */
                  if (lnext(l) != NULL)
                        do_text_output_oneline(tstate, "");
            }
      }

      end_tup_output(tstate);
}

/*
 * 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, ExplainStmt *stmt, const char *queryString,
                        ParamListInfo params, TupOutputState *tstate)
{
      /* planner will not cope with utility statements */
      if (query->commandType == CMD_UTILITY)
      {
            ExplainOneUtility(query->utilityStmt, stmt,
                                      queryString, params, tstate);
            return;
      }

      /* if an advisor plugin is present, let it manage things */
      if (ExplainOneQuery_hook)
            (*ExplainOneQuery_hook) (query, stmt, queryString, params, tstate);
      else
      {
            PlannedStmt *plan;

            /* plan the query */
            plan = pg_plan_query(query, 0, params);

            /* run it (if needed) and produce output */
            ExplainOnePlan(plan, stmt, queryString, params, tstate);
      }
}

/*
 * 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, ExplainStmt *stmt,
                          const char *queryString, ParamListInfo params,
                          TupOutputState *tstate)
{
      if (utilityStmt == NULL)
            return;

      if (IsA(utilityStmt, ExecuteStmt))
            ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
                                          queryString, params, tstate);
      else if (IsA(utilityStmt, NotifyStmt))
            do_text_output_oneline(tstate, "NOTIFY");
      else
            do_text_output_oneline(tstate,
                                             "Utility statements have no plan structure");
}

/*
 * 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, ExplainStmt *stmt,
                     const char *queryString, ParamListInfo params,
                     TupOutputState *tstate)
{
      QueryDesc  *queryDesc;
      instr_time  starttime;
      double            totaltime = 0;
      StringInfoData buf;
      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,
                                                stmt->analyze);

      INSTR_TIME_SET_CURRENT(starttime);

      /* If analyzing, we need to cope with queued triggers */
      if (stmt->analyze)
            AfterTriggerBeginQuery();

      /* Select execution options */
      if (stmt->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 (stmt->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 */
      initStringInfo(&buf);
      ExplainPrintPlan(&buf, queryDesc, stmt->analyze, stmt->verbose);

      /*
       * 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 (stmt->analyze)
      {
            INSTR_TIME_SET_CURRENT(starttime);
            AfterTriggerEndQuery(queryDesc->estate);
            totaltime += elapsed_time(&starttime);
      }

      /* Print info about runtime of triggers */
      if (stmt->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, &buf);

            foreach(l, targrels)
            {
                  rInfo = (ResultRelInfo *) lfirst(l);
                  report_triggers(rInfo, show_relname, &buf);
            }
      }

      /*
       * 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 (stmt->analyze)
            CommandCounterIncrement();

      totaltime += elapsed_time(&starttime);

      if (stmt->analyze)
            appendStringInfo(&buf, "Total runtime: %.3f ms\n",
                                     1000.0 * totaltime);
      do_text_output_multiline(tstate, buf.data);

      pfree(buf.data);
}

/*
 * ExplainPrintPlan -
 *      convert a QueryDesc's plan tree to text and append it to 'str'
 *
 * 'analyze' means to include runtime instrumentation results
 * 'verbose' means a verbose printout (currently, it shows targetlists)
 *
 * 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);
}

/*
 * 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);
}

/*
 * 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.
 * 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.
 */
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;
      }

      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(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
       * 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(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:
                  {
                        /*
                         * 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",
                                             ((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;
      }

      /* 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))
      {
            SubqueryScan *subqueryscan = (SubqueryScan *) plan;
            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;
      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 */
      for (i = 0; i < indent; i++)
            appendStringInfo(str, "  ");
      appendStringInfo(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)
                  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)
            return;

      /* Convert AND list to explicit AND */
      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);
}

/*
 * Show a qualifier expression for an upper-level plan node
 */
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,
                                                         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 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);
      }
}

/*
 * 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;
}

Generated by  Doxygen 1.6.0   Back to index