LCOV - code coverage report
Current view: top level - src/backend/commands - explain.c (source / functions) Hit Total Coverage
Test: PostgreSQL Lines: 1003 1532 65.5 %
Date: 2017-09-29 15:12:54 Functions: 49 60 81.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * explain.c
       4             :  *    Explain query execution plans
       5             :  *
       6             :  * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
       7             :  * Portions Copyright (c) 1994-5, Regents of the University of California
       8             :  *
       9             :  * IDENTIFICATION
      10             :  *    src/backend/commands/explain.c
      11             :  *
      12             :  *-------------------------------------------------------------------------
      13             :  */
      14             : #include "postgres.h"
      15             : 
      16             : #include "access/xact.h"
      17             : #include "catalog/pg_collation.h"
      18             : #include "catalog/pg_type.h"
      19             : #include "commands/createas.h"
      20             : #include "commands/defrem.h"
      21             : #include "commands/prepare.h"
      22             : #include "executor/hashjoin.h"
      23             : #include "foreign/fdwapi.h"
      24             : #include "nodes/extensible.h"
      25             : #include "nodes/nodeFuncs.h"
      26             : #include "optimizer/clauses.h"
      27             : #include "optimizer/planmain.h"
      28             : #include "parser/parsetree.h"
      29             : #include "rewrite/rewriteHandler.h"
      30             : #include "storage/bufmgr.h"
      31             : #include "tcop/tcopprot.h"
      32             : #include "utils/builtins.h"
      33             : #include "utils/json.h"
      34             : #include "utils/lsyscache.h"
      35             : #include "utils/rel.h"
      36             : #include "utils/ruleutils.h"
      37             : #include "utils/snapmgr.h"
      38             : #include "utils/tuplesort.h"
      39             : #include "utils/typcache.h"
      40             : #include "utils/xml.h"
      41             : 
      42             : 
      43             : /* Hook for plugins to get control in ExplainOneQuery() */
      44             : ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
      45             : 
      46             : /* Hook for plugins to get control in explain_get_index_name() */
      47             : explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
      48             : 
      49             : 
      50             : /* OR-able flags for ExplainXMLTag() */
      51             : #define X_OPENING 0
      52             : #define X_CLOSING 1
      53             : #define X_CLOSE_IMMEDIATE 2
      54             : #define X_NOWHITESPACE 4
      55             : 
      56             : static void ExplainOneQuery(Query *query, int cursorOptions,
      57             :                 IntoClause *into, ExplainState *es,
      58             :                 const char *queryString, ParamListInfo params,
      59             :                 QueryEnvironment *queryEnv);
      60             : static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
      61             :                 ExplainState *es);
      62             : static double elapsed_time(instr_time *starttime);
      63             : static bool ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used);
      64             : static void ExplainNode(PlanState *planstate, List *ancestors,
      65             :             const char *relationship, const char *plan_name,
      66             :             ExplainState *es);
      67             : static void show_plan_tlist(PlanState *planstate, List *ancestors,
      68             :                 ExplainState *es);
      69             : static void show_expression(Node *node, const char *qlabel,
      70             :                 PlanState *planstate, List *ancestors,
      71             :                 bool useprefix, ExplainState *es);
      72             : static void show_qual(List *qual, const char *qlabel,
      73             :           PlanState *planstate, List *ancestors,
      74             :           bool useprefix, ExplainState *es);
      75             : static void show_scan_qual(List *qual, const char *qlabel,
      76             :                PlanState *planstate, List *ancestors,
      77             :                ExplainState *es);
      78             : static void show_upper_qual(List *qual, const char *qlabel,
      79             :                 PlanState *planstate, List *ancestors,
      80             :                 ExplainState *es);
      81             : static void show_sort_keys(SortState *sortstate, List *ancestors,
      82             :                ExplainState *es);
      83             : static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
      84             :                        ExplainState *es);
      85             : static void show_agg_keys(AggState *astate, List *ancestors,
      86             :               ExplainState *es);
      87             : static void show_grouping_sets(PlanState *planstate, Agg *agg,
      88             :                    List *ancestors, ExplainState *es);
      89             : static void show_grouping_set_keys(PlanState *planstate,
      90             :                        Agg *aggnode, Sort *sortnode,
      91             :                        List *context, bool useprefix,
      92             :                        List *ancestors, ExplainState *es);
      93             : static void show_group_keys(GroupState *gstate, List *ancestors,
      94             :                 ExplainState *es);
      95             : static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
      96             :                      int nkeys, AttrNumber *keycols,
      97             :                      Oid *sortOperators, Oid *collations, bool *nullsFirst,
      98             :                      List *ancestors, ExplainState *es);
      99             : static void show_sortorder_options(StringInfo buf, Node *sortexpr,
     100             :                        Oid sortOperator, Oid collation, bool nullsFirst);
     101             : static void show_tablesample(TableSampleClause *tsc, PlanState *planstate,
     102             :                  List *ancestors, ExplainState *es);
     103             : static void show_sort_info(SortState *sortstate, ExplainState *es);
     104             : static void show_hash_info(HashState *hashstate, ExplainState *es);
     105             : static void show_tidbitmap_info(BitmapHeapScanState *planstate,
     106             :                     ExplainState *es);
     107             : static void show_instrumentation_count(const char *qlabel, int which,
     108             :                            PlanState *planstate, ExplainState *es);
     109             : static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
     110             : static const char *explain_get_index_name(Oid indexId);
     111             : static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
     112             : static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
     113             :                         ExplainState *es);
     114             : static void ExplainScanTarget(Scan *plan, ExplainState *es);
     115             : static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
     116             : static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
     117             : static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
     118             :                       ExplainState *es);
     119             : static void ExplainMemberNodes(List *plans, PlanState **planstates,
     120             :                    List *ancestors, ExplainState *es);
     121             : static void ExplainSubPlans(List *plans, List *ancestors,
     122             :                 const char *relationship, ExplainState *es);
     123             : static void ExplainCustomChildren(CustomScanState *css,
     124             :                       List *ancestors, ExplainState *es);
     125             : static void ExplainProperty(const char *qlabel, const char *value,
     126             :                 bool numeric, ExplainState *es);
     127             : static void ExplainOpenGroup(const char *objtype, const char *labelname,
     128             :                  bool labeled, ExplainState *es);
     129             : static void ExplainCloseGroup(const char *objtype, const char *labelname,
     130             :                   bool labeled, ExplainState *es);
     131             : static void ExplainDummyGroup(const char *objtype, const char *labelname,
     132             :                   ExplainState *es);
     133             : static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
     134             : static void ExplainJSONLineEnding(ExplainState *es);
     135             : static void ExplainYAMLLineStarting(ExplainState *es);
     136             : static void escape_yaml(StringInfo buf, const char *str);
     137             : 
     138             : 
     139             : 
     140             : /*
     141             :  * ExplainQuery -
     142             :  *    execute an EXPLAIN command
     143             :  */
     144             : void
     145        1123 : ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
     146             :              ParamListInfo params, QueryEnvironment *queryEnv,
     147             :              DestReceiver *dest)
     148             : {
     149        1123 :     ExplainState *es = NewExplainState();
     150             :     TupOutputState *tstate;
     151             :     List       *rewritten;
     152             :     ListCell   *lc;
     153        1123 :     bool        timing_set = false;
     154        1123 :     bool        summary_set = false;
     155             : 
     156             :     /* Parse options list. */
     157        1857 :     foreach(lc, stmt->options)
     158             :     {
     159         734 :         DefElem    *opt = (DefElem *) lfirst(lc);
     160             : 
     161         734 :         if (strcmp(opt->defname, "analyze") == 0)
     162           7 :             es->analyze = defGetBoolean(opt);
     163         727 :         else if (strcmp(opt->defname, "verbose") == 0)
     164          88 :             es->verbose = defGetBoolean(opt);
     165         639 :         else if (strcmp(opt->defname, "costs") == 0)
     166         623 :             es->costs = defGetBoolean(opt);
     167          16 :         else if (strcmp(opt->defname, "buffers") == 0)
     168           0 :             es->buffers = defGetBoolean(opt);
     169          16 :         else if (strcmp(opt->defname, "timing") == 0)
     170             :         {
     171           9 :             timing_set = true;
     172           9 :             es->timing = defGetBoolean(opt);
     173             :         }
     174           7 :         else if (strcmp(opt->defname, "summary") == 0)
     175             :         {
     176           6 :             summary_set = true;
     177           6 :             es->summary = defGetBoolean(opt);
     178             :         }
     179           1 :         else if (strcmp(opt->defname, "format") == 0)
     180             :         {
     181           1 :             char       *p = defGetString(opt);
     182             : 
     183           1 :             if (strcmp(p, "text") == 0)
     184           0 :                 es->format = EXPLAIN_FORMAT_TEXT;
     185           1 :             else if (strcmp(p, "xml") == 0)
     186           0 :                 es->format = EXPLAIN_FORMAT_XML;
     187           1 :             else if (strcmp(p, "json") == 0)
     188           1 :                 es->format = EXPLAIN_FORMAT_JSON;
     189           0 :             else if (strcmp(p, "yaml") == 0)
     190           0 :                 es->format = EXPLAIN_FORMAT_YAML;
     191             :             else
     192           0 :                 ereport(ERROR,
     193             :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     194             :                          errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
     195             :                                 opt->defname, p),
     196             :                          parser_errposition(pstate, opt->location)));
     197             :         }
     198             :         else
     199           0 :             ereport(ERROR,
     200             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     201             :                      errmsg("unrecognized EXPLAIN option \"%s\"",
     202             :                             opt->defname),
     203             :                      parser_errposition(pstate, opt->location)));
     204             :     }
     205             : 
     206        1123 :     if (es->buffers && !es->analyze)
     207           0 :         ereport(ERROR,
     208             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     209             :                  errmsg("EXPLAIN option BUFFERS requires ANALYZE")));
     210             : 
     211             :     /* if the timing was not set explicitly, set default value */
     212        1123 :     es->timing = (timing_set) ? es->timing : es->analyze;
     213             : 
     214             :     /* check that timing is used with EXPLAIN ANALYZE */
     215        1123 :     if (es->timing && !es->analyze)
     216           0 :         ereport(ERROR,
     217             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     218             :                  errmsg("EXPLAIN option TIMING requires ANALYZE")));
     219             : 
     220             :     /* if the summary was not set explicitly, set default value */
     221        1123 :     es->summary = (summary_set) ? es->summary : es->analyze;
     222             : 
     223             :     /*
     224             :      * Parse analysis was done already, but we still have to run the rule
     225             :      * rewriter.  We do not do AcquireRewriteLocks: we assume the query either
     226             :      * came straight from the parser, or suitable locks were acquired by
     227             :      * plancache.c.
     228             :      *
     229             :      * Because the rewriter and planner tend to scribble on the input, we make
     230             :      * a preliminary copy of the source querytree.  This prevents problems in
     231             :      * the case that the EXPLAIN is in a portal or plpgsql function and is
     232             :      * executed repeatedly.  (See also the same hack in DECLARE CURSOR and
     233             :      * PREPARE.)  XXX FIXME someday.
     234             :      */
     235        1123 :     rewritten = QueryRewrite(castNode(Query, copyObject(stmt->query)));
     236             : 
     237             :     /* emit opening boilerplate */
     238        1123 :     ExplainBeginOutput(es);
     239             : 
     240        1123 :     if (rewritten == NIL)
     241             :     {
     242             :         /*
     243             :          * In the case of an INSTEAD NOTHING, tell at least that.  But in
     244             :          * non-text format, the output is delimited, so this isn't necessary.
     245             :          */
     246           0 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     247           0 :             appendStringInfoString(es->str, "Query rewrites to nothing\n");
     248             :     }
     249             :     else
     250             :     {
     251             :         ListCell   *l;
     252             : 
     253             :         /* Explain every plan */
     254        2241 :         foreach(l, rewritten)
     255             :         {
     256        1125 :             ExplainOneQuery(lfirst_node(Query, l),
     257             :                             CURSOR_OPT_PARALLEL_OK, NULL, es,
     258             :                             queryString, params, queryEnv);
     259             : 
     260             :             /* Separate plans with an appropriate separator */
     261        1118 :             if (lnext(l) != NULL)
     262           2 :                 ExplainSeparatePlans(es);
     263             :         }
     264             :     }
     265             : 
     266             :     /* emit closing boilerplate */
     267        1116 :     ExplainEndOutput(es);
     268        1116 :     Assert(es->indent == 0);
     269             : 
     270             :     /* output tuples */
     271        1116 :     tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
     272        1116 :     if (es->format == EXPLAIN_FORMAT_TEXT)
     273        1115 :         do_text_output_multiline(tstate, es->str->data);
     274             :     else
     275           1 :         do_text_output_oneline(tstate, es->str->data);
     276        1116 :     end_tup_output(tstate);
     277             : 
     278        1116 :     pfree(es->str->data);
     279        1116 : }
     280             : 
     281             : /*
     282             :  * Create a new ExplainState struct initialized with default options.
     283             :  */
     284             : ExplainState *
     285        1123 : NewExplainState(void)
     286             : {
     287        1123 :     ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));
     288             : 
     289             :     /* Set default options (most fields can be left as zeroes). */
     290        1123 :     es->costs = true;
     291             :     /* Prepare output buffer. */
     292        1123 :     es->str = makeStringInfo();
     293             : 
     294        1123 :     return es;
     295             : }
     296             : 
     297             : /*
     298             :  * ExplainResultDesc -
     299             :  *    construct the result tupledesc for an EXPLAIN
     300             :  */
     301             : TupleDesc
     302        2739 : ExplainResultDesc(ExplainStmt *stmt)
     303             : {
     304             :     TupleDesc   tupdesc;
     305             :     ListCell   *lc;
     306        2739 :     Oid         result_type = TEXTOID;
     307             : 
     308             :     /* Check for XML format option */
     309        4211 :     foreach(lc, stmt->options)
     310             :     {
     311        1472 :         DefElem    *opt = (DefElem *) lfirst(lc);
     312             : 
     313        1472 :         if (strcmp(opt->defname, "format") == 0)
     314             :         {
     315           2 :             char       *p = defGetString(opt);
     316             : 
     317           2 :             if (strcmp(p, "xml") == 0)
     318           0 :                 result_type = XMLOID;
     319           2 :             else if (strcmp(p, "json") == 0)
     320           2 :                 result_type = JSONOID;
     321             :             else
     322           0 :                 result_type = TEXTOID;
     323             :             /* don't "break", as ExplainQuery will use the last value */
     324             :         }
     325             :     }
     326             : 
     327             :     /* Need a tuple descriptor representing a single TEXT or XML column */
     328        2739 :     tupdesc = CreateTemplateTupleDesc(1, false);
     329        2739 :     TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
     330             :                        result_type, -1, 0);
     331        2739 :     return tupdesc;
     332             : }
     333             : 
     334             : /*
     335             :  * ExplainOneQuery -
     336             :  *    print out the execution plan for one Query
     337             :  *
     338             :  * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
     339             :  */
     340             : static void
     341        1133 : ExplainOneQuery(Query *query, int cursorOptions,
     342             :                 IntoClause *into, ExplainState *es,
     343             :                 const char *queryString, ParamListInfo params,
     344             :                 QueryEnvironment *queryEnv)
     345             : {
     346             :     /* planner will not cope with utility statements */
     347        1133 :     if (query->commandType == CMD_UTILITY)
     348             :     {
     349          27 :         ExplainOneUtility(query->utilityStmt, into, es, queryString, params,
     350             :                           queryEnv);
     351        1153 :         return;
     352             :     }
     353             : 
     354             :     /* if an advisor plugin is present, let it manage things */
     355        1106 :     if (ExplainOneQuery_hook)
     356           0 :         (*ExplainOneQuery_hook) (query, cursorOptions, into, es,
     357             :                                  queryString, params);
     358             :     else
     359             :     {
     360             :         PlannedStmt *plan;
     361             :         instr_time  planstart,
     362             :                     planduration;
     363             : 
     364        1106 :         INSTR_TIME_SET_CURRENT(planstart);
     365             : 
     366             :         /* plan the query */
     367        1106 :         plan = pg_plan_query(query, cursorOptions, params);
     368             : 
     369        1102 :         INSTR_TIME_SET_CURRENT(planduration);
     370        1102 :         INSTR_TIME_SUBTRACT(planduration, planstart);
     371             : 
     372             :         /* run it (if needed) and produce output */
     373        1102 :         ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
     374             :                        &planduration);
     375             :     }
     376             : }
     377             : 
     378             : /*
     379             :  * ExplainOneUtility -
     380             :  *    print out the execution plan for one utility statement
     381             :  *    (In general, utility statements don't have plans, but there are some
     382             :  *    we treat as special cases)
     383             :  *
     384             :  * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
     385             :  *
     386             :  * This is exported because it's called back from prepare.c in the
     387             :  * EXPLAIN EXECUTE case.
     388             :  */
     389             : void
     390          27 : ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
     391             :                   const char *queryString, ParamListInfo params,
     392             :                   QueryEnvironment *queryEnv)
     393             : {
     394          27 :     if (utilityStmt == NULL)
     395          27 :         return;
     396             : 
     397          27 :     if (IsA(utilityStmt, CreateTableAsStmt))
     398             :     {
     399             :         /*
     400             :          * We have to rewrite the contained SELECT and then pass it back to
     401             :          * ExplainOneQuery.  It's probably not really necessary to copy the
     402             :          * contained parsetree another time, but let's be safe.
     403             :          *
     404             :          * Like ExecCreateTableAs, disallow parallelism in the plan.
     405             :          */
     406           4 :         CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
     407             :         List       *rewritten;
     408             : 
     409           4 :         rewritten = QueryRewrite(castNode(Query, copyObject(ctas->query)));
     410           4 :         Assert(list_length(rewritten) == 1);
     411           4 :         ExplainOneQuery(linitial_node(Query, rewritten),
     412             :                         0, ctas->into, es,
     413             :                         queryString, params, queryEnv);
     414             :     }
     415          23 :     else if (IsA(utilityStmt, DeclareCursorStmt))
     416             :     {
     417             :         /*
     418             :          * Likewise for DECLARE CURSOR.
     419             :          *
     420             :          * Notice that if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll
     421             :          * actually run the query.  This is different from pre-8.3 behavior
     422             :          * but seems more useful than not running the query.  No cursor will
     423             :          * be created, however.
     424             :          */
     425           4 :         DeclareCursorStmt *dcs = (DeclareCursorStmt *) utilityStmt;
     426             :         List       *rewritten;
     427             : 
     428           4 :         rewritten = QueryRewrite(castNode(Query, copyObject(dcs->query)));
     429           4 :         Assert(list_length(rewritten) == 1);
     430           4 :         ExplainOneQuery(linitial_node(Query, rewritten),
     431             :                         dcs->options, NULL, es,
     432             :                         queryString, params, queryEnv);
     433             :     }
     434          19 :     else if (IsA(utilityStmt, ExecuteStmt))
     435          19 :         ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
     436             :                             queryString, params, queryEnv);
     437           0 :     else if (IsA(utilityStmt, NotifyStmt))
     438             :     {
     439           0 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     440           0 :             appendStringInfoString(es->str, "NOTIFY\n");
     441             :         else
     442           0 :             ExplainDummyGroup("Notify", NULL, es);
     443             :     }
     444             :     else
     445             :     {
     446           0 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     447           0 :             appendStringInfoString(es->str,
     448             :                                    "Utility statements have no plan structure\n");
     449             :         else
     450           0 :             ExplainDummyGroup("Utility Statement", NULL, es);
     451             :     }
     452             : }
     453             : 
     454             : /*
     455             :  * ExplainOnePlan -
     456             :  *      given a planned query, execute it if needed, and then print
     457             :  *      EXPLAIN output
     458             :  *
     459             :  * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt,
     460             :  * in which case executing the query should result in creating that table.
     461             :  *
     462             :  * This is exported because it's called back from prepare.c in the
     463             :  * EXPLAIN EXECUTE case, and because an index advisor plugin would need
     464             :  * to call it.
     465             :  */
     466             : void
     467        1121 : ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
     468             :                const char *queryString, ParamListInfo params,
     469             :                QueryEnvironment *queryEnv, const instr_time *planduration)
     470             : {
     471             :     DestReceiver *dest;
     472             :     QueryDesc  *queryDesc;
     473             :     instr_time  starttime;
     474        1121 :     double      totaltime = 0;
     475             :     int         eflags;
     476        1121 :     int         instrument_option = 0;
     477             : 
     478        1121 :     Assert(plannedstmt->commandType != CMD_UTILITY);
     479             : 
     480        1121 :     if (es->analyze && es->timing)
     481           1 :         instrument_option |= INSTRUMENT_TIMER;
     482        1120 :     else if (es->analyze)
     483           6 :         instrument_option |= INSTRUMENT_ROWS;
     484             : 
     485        1121 :     if (es->buffers)
     486           0 :         instrument_option |= INSTRUMENT_BUFFERS;
     487             : 
     488             :     /*
     489             :      * We always collect timing for the entire statement, even when node-level
     490             :      * timing is off, so we don't look at es->timing here.  (We could skip
     491             :      * this if !es->summary, but it's hardly worth the complication.)
     492             :      */
     493        1121 :     INSTR_TIME_SET_CURRENT(starttime);
     494             : 
     495             :     /*
     496             :      * Use a snapshot with an updated command ID to ensure this query sees
     497             :      * results of any previously executed queries.
     498             :      */
     499        1121 :     PushCopiedSnapshot(GetActiveSnapshot());
     500        1121 :     UpdateActiveSnapshotCommandId();
     501             : 
     502             :     /*
     503             :      * Normally we discard the query's output, but if explaining CREATE TABLE
     504             :      * AS, we'd better use the appropriate tuple receiver.
     505             :      */
     506        1121 :     if (into)
     507           4 :         dest = CreateIntoRelDestReceiver(into);
     508             :     else
     509        1117 :         dest = None_Receiver;
     510             : 
     511             :     /* Create a QueryDesc for the query */
     512        1121 :     queryDesc = CreateQueryDesc(plannedstmt, queryString,
     513             :                                 GetActiveSnapshot(), InvalidSnapshot,
     514             :                                 dest, params, queryEnv, instrument_option);
     515             : 
     516             :     /* Select execution options */
     517        1121 :     if (es->analyze)
     518           7 :         eflags = 0;             /* default run-to-completion flags */
     519             :     else
     520        1114 :         eflags = EXEC_FLAG_EXPLAIN_ONLY;
     521        1121 :     if (into)
     522           4 :         eflags |= GetIntoRelEFlags(into);
     523             : 
     524             :     /* call ExecutorStart to prepare the plan for execution */
     525        1121 :     ExecutorStart(queryDesc, eflags);
     526             : 
     527             :     /* Execute the plan for statistics if asked for */
     528        1119 :     if (es->analyze)
     529             :     {
     530             :         ScanDirection dir;
     531             : 
     532             :         /* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */
     533           7 :         if (into && into->skipData)
     534           0 :             dir = NoMovementScanDirection;
     535             :         else
     536           7 :             dir = ForwardScanDirection;
     537             : 
     538             :         /* run the plan */
     539           7 :         ExecutorRun(queryDesc, dir, 0L, true);
     540             : 
     541             :         /* run cleanup too */
     542           6 :         ExecutorFinish(queryDesc);
     543             : 
     544             :         /* We can't run ExecutorEnd 'till we're done printing the stats... */
     545           6 :         totaltime += elapsed_time(&starttime);
     546             :     }
     547             : 
     548        1118 :     ExplainOpenGroup("Query", NULL, true, es);
     549             : 
     550             :     /* Create textual dump of plan tree */
     551        1118 :     ExplainPrintPlan(es, queryDesc);
     552             : 
     553        1118 :     if (es->summary && planduration)
     554             :     {
     555           1 :         double      plantime = INSTR_TIME_GET_DOUBLE(*planduration);
     556             : 
     557           1 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     558           1 :             appendStringInfo(es->str, "Planning time: %.3f ms\n",
     559             :                              1000.0 * plantime);
     560             :         else
     561           0 :             ExplainPropertyFloat("Planning Time", 1000.0 * plantime, 3, es);
     562             :     }
     563             : 
     564             :     /* Print info about runtime of triggers */
     565        1118 :     if (es->analyze)
     566           6 :         ExplainPrintTriggers(es, queryDesc);
     567             : 
     568             :     /*
     569             :      * Close down the query and free resources.  Include time for this in the
     570             :      * total execution time (although it should be pretty minimal).
     571             :      */
     572        1118 :     INSTR_TIME_SET_CURRENT(starttime);
     573             : 
     574        1118 :     ExecutorEnd(queryDesc);
     575             : 
     576        1118 :     FreeQueryDesc(queryDesc);
     577             : 
     578        1118 :     PopActiveSnapshot();
     579             : 
     580             :     /* We need a CCI just in case query expanded to multiple plans */
     581        1118 :     if (es->analyze)
     582           6 :         CommandCounterIncrement();
     583             : 
     584        1118 :     totaltime += elapsed_time(&starttime);
     585             : 
     586             :     /*
     587             :      * We only report execution time if we actually ran the query (that is,
     588             :      * the user specified ANALYZE), and if summary reporting is enabled (the
     589             :      * user can set SUMMARY OFF to not have the timing information included in
     590             :      * the output).  By default, ANALYZE sets SUMMARY to true.
     591             :      */
     592        1118 :     if (es->summary && es->analyze)
     593             :     {
     594           1 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     595           1 :             appendStringInfo(es->str, "Execution time: %.3f ms\n",
     596             :                              1000.0 * totaltime);
     597             :         else
     598           0 :             ExplainPropertyFloat("Execution Time", 1000.0 * totaltime,
     599             :                                  3, es);
     600             :     }
     601             : 
     602        1118 :     ExplainCloseGroup("Query", NULL, true, es);
     603        1118 : }
     604             : 
     605             : /*
     606             :  * ExplainPrintPlan -
     607             :  *    convert a QueryDesc's plan tree to text and append it to es->str
     608             :  *
     609             :  * The caller should have set up the options fields of *es, as well as
     610             :  * initializing the output buffer es->str.  Also, output formatting state
     611             :  * such as the indent level is assumed valid.  Plan-tree-specific fields
     612             :  * in *es are initialized here.
     613             :  *
     614             :  * NB: will not work on utility statements
     615             :  */
     616             : void
     617        1118 : ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
     618             : {
     619        1118 :     Bitmapset  *rels_used = NULL;
     620             :     PlanState  *ps;
     621             : 
     622             :     /* Set up ExplainState fields associated with this plan tree */
     623        1118 :     Assert(queryDesc->plannedstmt != NULL);
     624        1118 :     es->pstmt = queryDesc->plannedstmt;
     625        1118 :     es->rtable = queryDesc->plannedstmt->rtable;
     626        1118 :     ExplainPreScanNode(queryDesc->planstate, &rels_used);
     627        1118 :     es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used);
     628        1118 :     es->deparse_cxt = deparse_context_for_plan_rtable(es->rtable,
     629             :                                                       es->rtable_names);
     630        1118 :     es->printed_subplans = NULL;
     631             : 
     632             :     /*
     633             :      * Sometimes we mark a Gather node as "invisible", which means that it's
     634             :      * not displayed in EXPLAIN output.  The purpose of this is to allow
     635             :      * running regression tests with force_parallel_mode=regress to get the
     636             :      * same results as running the same tests with force_parallel_mode=off.
     637             :      */
     638        1118 :     ps = queryDesc->planstate;
     639        1118 :     if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
     640           0 :         ps = outerPlanState(ps);
     641        1118 :     ExplainNode(ps, NIL, NULL, NULL, es);
     642        1118 : }
     643             : 
     644             : /*
     645             :  * ExplainPrintTriggers -
     646             :  *    convert a QueryDesc's trigger statistics to text and append it to
     647             :  *    es->str
     648             :  *
     649             :  * The caller should have set up the options fields of *es, as well as
     650             :  * initializing the output buffer es->str.  Other fields in *es are
     651             :  * initialized here.
     652             :  */
     653             : void
     654           6 : ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
     655             : {
     656             :     ResultRelInfo *rInfo;
     657             :     bool        show_relname;
     658           6 :     int         numrels = queryDesc->estate->es_num_result_relations;
     659           6 :     int         numrootrels = queryDesc->estate->es_num_root_result_relations;
     660           6 :     List       *leafrels = queryDesc->estate->es_leaf_result_relations;
     661           6 :     List       *targrels = queryDesc->estate->es_trig_target_relations;
     662             :     int         nr;
     663             :     ListCell   *l;
     664             : 
     665           6 :     ExplainOpenGroup("Triggers", "Triggers", false, es);
     666             : 
     667          12 :     show_relname = (numrels > 1 || numrootrels > 0 ||
     668          12 :                     leafrels != NIL || targrels != NIL);
     669           6 :     rInfo = queryDesc->estate->es_result_relations;
     670           8 :     for (nr = 0; nr < numrels; rInfo++, nr++)
     671           2 :         report_triggers(rInfo, show_relname, es);
     672             : 
     673           6 :     rInfo = queryDesc->estate->es_root_result_relations;
     674           6 :     for (nr = 0; nr < numrootrels; rInfo++, nr++)
     675           0 :         report_triggers(rInfo, show_relname, es);
     676             : 
     677           6 :     foreach(l, leafrels)
     678             :     {
     679           0 :         rInfo = (ResultRelInfo *) lfirst(l);
     680           0 :         report_triggers(rInfo, show_relname, es);
     681             :     }
     682             : 
     683           6 :     foreach(l, targrels)
     684             :     {
     685           0 :         rInfo = (ResultRelInfo *) lfirst(l);
     686           0 :         report_triggers(rInfo, show_relname, es);
     687             :     }
     688             : 
     689           6 :     ExplainCloseGroup("Triggers", "Triggers", false, es);
     690           6 : }
     691             : 
     692             : /*
     693             :  * ExplainQueryText -
     694             :  *    add a "Query Text" node that contains the actual text of the query
     695             :  *
     696             :  * The caller should have set up the options fields of *es, as well as
     697             :  * initializing the output buffer es->str.
     698             :  *
     699             :  */
     700             : void
     701           0 : ExplainQueryText(ExplainState *es, QueryDesc *queryDesc)
     702             : {
     703           0 :     if (queryDesc->sourceText)
     704           0 :         ExplainPropertyText("Query Text", queryDesc->sourceText, es);
     705           0 : }
     706             : 
     707             : /*
     708             :  * report_triggers -
     709             :  *      report execution stats for a single relation's triggers
     710             :  */
     711             : static void
     712           2 : report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es)
     713             : {
     714             :     int         nt;
     715             : 
     716           2 :     if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
     717           4 :         return;
     718           0 :     for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
     719             :     {
     720           0 :         Trigger    *trig = rInfo->ri_TrigDesc->triggers + nt;
     721           0 :         Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
     722             :         char       *relname;
     723           0 :         char       *conname = NULL;
     724             : 
     725             :         /* Must clean up instrumentation state */
     726           0 :         InstrEndLoop(instr);
     727             : 
     728             :         /*
     729             :          * We ignore triggers that were never invoked; they likely aren't
     730             :          * relevant to the current query type.
     731             :          */
     732           0 :         if (instr->ntuples == 0)
     733           0 :             continue;
     734             : 
     735           0 :         ExplainOpenGroup("Trigger", NULL, true, es);
     736             : 
     737           0 :         relname = RelationGetRelationName(rInfo->ri_RelationDesc);
     738           0 :         if (OidIsValid(trig->tgconstraint))
     739           0 :             conname = get_constraint_name(trig->tgconstraint);
     740             : 
     741             :         /*
     742             :          * In text format, we avoid printing both the trigger name and the
     743             :          * constraint name unless VERBOSE is specified.  In non-text formats
     744             :          * we just print everything.
     745             :          */
     746           0 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     747             :         {
     748           0 :             if (es->verbose || conname == NULL)
     749           0 :                 appendStringInfo(es->str, "Trigger %s", trig->tgname);
     750             :             else
     751           0 :                 appendStringInfoString(es->str, "Trigger");
     752           0 :             if (conname)
     753           0 :                 appendStringInfo(es->str, " for constraint %s", conname);
     754           0 :             if (show_relname)
     755           0 :                 appendStringInfo(es->str, " on %s", relname);
     756           0 :             if (es->timing)
     757           0 :                 appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",
     758           0 :                                  1000.0 * instr->total, instr->ntuples);
     759             :             else
     760           0 :                 appendStringInfo(es->str, ": calls=%.0f\n", instr->ntuples);
     761             :         }
     762             :         else
     763             :         {
     764           0 :             ExplainPropertyText("Trigger Name", trig->tgname, es);
     765           0 :             if (conname)
     766           0 :                 ExplainPropertyText("Constraint Name", conname, es);
     767           0 :             ExplainPropertyText("Relation", relname, es);
     768           0 :             if (es->timing)
     769           0 :                 ExplainPropertyFloat("Time", 1000.0 * instr->total, 3, es);
     770           0 :             ExplainPropertyFloat("Calls", instr->ntuples, 0, es);
     771             :         }
     772             : 
     773           0 :         if (conname)
     774           0 :             pfree(conname);
     775             : 
     776           0 :         ExplainCloseGroup("Trigger", NULL, true, es);
     777             :     }
     778             : }
     779             : 
     780             : /* Compute elapsed time in seconds since given timestamp */
     781             : static double
     782        1124 : elapsed_time(instr_time *starttime)
     783             : {
     784             :     instr_time  endtime;
     785             : 
     786        1124 :     INSTR_TIME_SET_CURRENT(endtime);
     787        1124 :     INSTR_TIME_SUBTRACT(endtime, *starttime);
     788        1124 :     return INSTR_TIME_GET_DOUBLE(endtime);
     789             : }
     790             : 
     791             : /*
     792             :  * ExplainPreScanNode -
     793             :  *    Prescan the planstate tree to identify which RTEs are referenced
     794             :  *
     795             :  * Adds the relid of each referenced RTE to *rels_used.  The result controls
     796             :  * which RTEs are assigned aliases by select_rtable_names_for_explain.
     797             :  * This ensures that we don't confusingly assign un-suffixed aliases to RTEs
     798             :  * that never appear in the EXPLAIN output (such as inheritance parents).
     799             :  */
     800             : static bool
     801        3420 : ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
     802             : {
     803        3420 :     Plan       *plan = planstate->plan;
     804             : 
     805        3420 :     switch (nodeTag(plan))
     806             :     {
     807             :         case T_SeqScan:
     808             :         case T_SampleScan:
     809             :         case T_IndexScan:
     810             :         case T_IndexOnlyScan:
     811             :         case T_BitmapHeapScan:
     812             :         case T_TidScan:
     813             :         case T_SubqueryScan:
     814             :         case T_FunctionScan:
     815             :         case T_TableFuncScan:
     816             :         case T_ValuesScan:
     817             :         case T_CteScan:
     818             :         case T_NamedTuplestoreScan:
     819             :         case T_WorkTableScan:
     820        1598 :             *rels_used = bms_add_member(*rels_used,
     821        1598 :                                         ((Scan *) plan)->scanrelid);
     822        1598 :             break;
     823             :         case T_ForeignScan:
     824           0 :             *rels_used = bms_add_members(*rels_used,
     825           0 :                                          ((ForeignScan *) plan)->fs_relids);
     826           0 :             break;
     827             :         case T_CustomScan:
     828           0 :             *rels_used = bms_add_members(*rels_used,
     829           0 :                                          ((CustomScan *) plan)->custom_relids);
     830           0 :             break;
     831             :         case T_ModifyTable:
     832          61 :             *rels_used = bms_add_member(*rels_used,
     833          61 :                                         ((ModifyTable *) plan)->nominalRelation);
     834          61 :             if (((ModifyTable *) plan)->exclRelRTI)
     835          11 :                 *rels_used = bms_add_member(*rels_used,
     836          11 :                                             ((ModifyTable *) plan)->exclRelRTI);
     837          61 :             break;
     838             :         default:
     839        1761 :             break;
     840             :     }
     841             : 
     842        3420 :     return planstate_tree_walker(planstate, ExplainPreScanNode, rels_used);
     843             : }
     844             : 
     845             : /*
     846             :  * ExplainNode -
     847             :  *    Appends a description of a plan tree to es->str
     848             :  *
     849             :  * planstate points to the executor state node for the current plan node.
     850             :  * We need to work from a PlanState node, not just a Plan node, in order to
     851             :  * get at the instrumentation data (if any) as well as the list of subplans.
     852             :  *
     853             :  * ancestors is a list of parent PlanState nodes, most-closely-nested first.
     854             :  * These are needed in order to interpret PARAM_EXEC Params.
     855             :  *
     856             :  * relationship describes the relationship of this plan node to its parent
     857             :  * (eg, "Outer", "Inner"); it can be null at top level.  plan_name is an
     858             :  * optional name to be attached to the node.
     859             :  *
     860             :  * In text format, es->indent is controlled in this function since we only
     861             :  * want it to change at plan-node boundaries.  In non-text formats, es->indent
     862             :  * corresponds to the nesting depth of logical output groups, and therefore
     863             :  * is controlled by ExplainOpenGroup/ExplainCloseGroup.
     864             :  */
     865             : static void
     866        3384 : ExplainNode(PlanState *planstate, List *ancestors,
     867             :             const char *relationship, const char *plan_name,
     868             :             ExplainState *es)
     869             : {
     870        3384 :     Plan       *plan = planstate->plan;
     871             :     const char *pname;          /* node type name for text output */
     872             :     const char *sname;          /* node type name for non-text output */
     873        3384 :     const char *strategy = NULL;
     874        3384 :     const char *partialmode = NULL;
     875        3384 :     const char *operation = NULL;
     876        3384 :     const char *custom_name = NULL;
     877        3384 :     int         save_indent = es->indent;
     878             :     bool        haschildren;
     879             : 
     880        3384 :     switch (nodeTag(plan))
     881             :     {
     882             :         case T_Result:
     883         108 :             pname = sname = "Result";
     884         108 :             break;
     885             :         case T_ProjectSet:
     886          17 :             pname = sname = "ProjectSet";
     887          17 :             break;
     888             :         case T_ModifyTable:
     889          61 :             sname = "ModifyTable";
     890          61 :             switch (((ModifyTable *) plan)->operation)
     891             :             {
     892             :                 case CMD_INSERT:
     893          24 :                     pname = operation = "Insert";
     894          24 :                     break;
     895             :                 case CMD_UPDATE:
     896          25 :                     pname = operation = "Update";
     897          25 :                     break;
     898             :                 case CMD_DELETE:
     899          12 :                     pname = operation = "Delete";
     900          12 :                     break;
     901             :                 default:
     902           0 :                     pname = "???";
     903           0 :                     break;
     904             :             }
     905          61 :             break;
     906             :         case T_Append:
     907          80 :             pname = sname = "Append";
     908          80 :             break;
     909             :         case T_MergeAppend:
     910          22 :             pname = sname = "Merge Append";
     911          22 :             break;
     912             :         case T_RecursiveUnion:
     913           0 :             pname = sname = "Recursive Union";
     914           0 :             break;
     915             :         case T_BitmapAnd:
     916           1 :             pname = sname = "BitmapAnd";
     917           1 :             break;
     918             :         case T_BitmapOr:
     919          10 :             pname = sname = "BitmapOr";
     920          10 :             break;
     921             :         case T_NestLoop:
     922         167 :             pname = sname = "Nested Loop";
     923         167 :             break;
     924             :         case T_MergeJoin:
     925          14 :             pname = "Merge";  /* "Join" gets added by jointype switch */
     926          14 :             sname = "Merge Join";
     927          14 :             break;
     928             :         case T_HashJoin:
     929          53 :             pname = "Hash";       /* "Join" gets added by jointype switch */
     930          53 :             sname = "Hash Join";
     931          53 :             break;
     932             :         case T_SeqScan:
     933         813 :             pname = sname = "Seq Scan";
     934         813 :             break;
     935             :         case T_SampleScan:
     936           9 :             pname = sname = "Sample Scan";
     937           9 :             break;
     938             :         case T_Gather:
     939          14 :             pname = sname = "Gather";
     940          14 :             break;
     941             :         case T_GatherMerge:
     942           4 :             pname = sname = "Gather Merge";
     943           4 :             break;
     944             :         case T_IndexScan:
     945         164 :             pname = sname = "Index Scan";
     946         164 :             break;
     947             :         case T_IndexOnlyScan:
     948         160 :             pname = sname = "Index Only Scan";
     949         160 :             break;
     950             :         case T_BitmapIndexScan:
     951         327 :             pname = sname = "Bitmap Index Scan";
     952         327 :             break;
     953             :         case T_BitmapHeapScan:
     954         314 :             pname = sname = "Bitmap Heap Scan";
     955         314 :             break;
     956             :         case T_TidScan:
     957           7 :             pname = sname = "Tid Scan";
     958           7 :             break;
     959             :         case T_SubqueryScan:
     960          36 :             pname = sname = "Subquery Scan";
     961          36 :             break;
     962             :         case T_FunctionScan:
     963          23 :             pname = sname = "Function Scan";
     964          23 :             break;
     965             :         case T_TableFuncScan:
     966           5 :             pname = sname = "Table Function Scan";
     967           5 :             break;
     968             :         case T_ValuesScan:
     969          24 :             pname = sname = "Values Scan";
     970          24 :             break;
     971             :         case T_CteScan:
     972          15 :             pname = sname = "CTE Scan";
     973          15 :             break;
     974             :         case T_NamedTuplestoreScan:
     975           4 :             pname = sname = "Named Tuplestore Scan";
     976           4 :             break;
     977             :         case T_WorkTableScan:
     978           0 :             pname = sname = "WorkTable Scan";
     979           0 :             break;
     980             :         case T_ForeignScan:
     981           0 :             sname = "Foreign Scan";
     982           0 :             switch (((ForeignScan *) plan)->operation)
     983             :             {
     984             :                 case CMD_SELECT:
     985           0 :                     pname = "Foreign Scan";
     986           0 :                     operation = "Select";
     987           0 :                     break;
     988             :                 case CMD_INSERT:
     989           0 :                     pname = "Foreign Insert";
     990           0 :                     operation = "Insert";
     991           0 :                     break;
     992             :                 case CMD_UPDATE:
     993           0 :                     pname = "Foreign Update";
     994           0 :                     operation = "Update";
     995           0 :                     break;
     996             :                 case CMD_DELETE:
     997           0 :                     pname = "Foreign Delete";
     998           0 :                     operation = "Delete";
     999           0 :                     break;
    1000             :                 default:
    1001           0 :                     pname = "???";
    1002           0 :                     break;
    1003             :             }
    1004           0 :             break;
    1005             :         case T_CustomScan:
    1006           0 :             sname = "Custom Scan";
    1007           0 :             custom_name = ((CustomScan *) plan)->methods->CustomName;
    1008           0 :             if (custom_name)
    1009           0 :                 pname = psprintf("Custom Scan (%s)", custom_name);
    1010             :             else
    1011           0 :                 pname = sname;
    1012           0 :             break;
    1013             :         case T_Material:
    1014          35 :             pname = sname = "Materialize";
    1015          35 :             break;
    1016             :         case T_Sort:
    1017          98 :             pname = sname = "Sort";
    1018          98 :             break;
    1019             :         case T_Group:
    1020           2 :             pname = sname = "Group";
    1021           2 :             break;
    1022             :         case T_Agg:
    1023             :             {
    1024         674 :                 Agg        *agg = (Agg *) plan;
    1025             : 
    1026         674 :                 sname = "Aggregate";
    1027         674 :                 switch (agg->aggstrategy)
    1028             :                 {
    1029             :                     case AGG_PLAIN:
    1030         609 :                         pname = "Aggregate";
    1031         609 :                         strategy = "Plain";
    1032         609 :                         break;
    1033             :                     case AGG_SORTED:
    1034          17 :                         pname = "GroupAggregate";
    1035          17 :                         strategy = "Sorted";
    1036          17 :                         break;
    1037             :                     case AGG_HASHED:
    1038          39 :                         pname = "HashAggregate";
    1039          39 :                         strategy = "Hashed";
    1040          39 :                         break;
    1041             :                     case AGG_MIXED:
    1042           9 :                         pname = "MixedAggregate";
    1043           9 :                         strategy = "Mixed";
    1044           9 :                         break;
    1045             :                     default:
    1046           0 :                         pname = "Aggregate ???";
    1047           0 :                         strategy = "???";
    1048           0 :                         break;
    1049             :                 }
    1050             : 
    1051         674 :                 if (DO_AGGSPLIT_SKIPFINAL(agg->aggsplit))
    1052             :                 {
    1053          11 :                     partialmode = "Partial";
    1054          11 :                     pname = psprintf("%s %s", partialmode, pname);
    1055             :                 }
    1056         663 :                 else if (DO_AGGSPLIT_COMBINE(agg->aggsplit))
    1057             :                 {
    1058          11 :                     partialmode = "Finalize";
    1059          11 :                     pname = psprintf("%s %s", partialmode, pname);
    1060             :                 }
    1061             :                 else
    1062         652 :                     partialmode = "Simple";
    1063             :             }
    1064         674 :             break;
    1065             :         case T_WindowAgg:
    1066           6 :             pname = sname = "WindowAgg";
    1067           6 :             break;
    1068             :         case T_Unique:
    1069           5 :             pname = sname = "Unique";
    1070           5 :             break;
    1071             :         case T_SetOp:
    1072           4 :             sname = "SetOp";
    1073           4 :             switch (((SetOp *) plan)->strategy)
    1074             :             {
    1075             :                 case SETOP_SORTED:
    1076           2 :                     pname = "SetOp";
    1077           2 :                     strategy = "Sorted";
    1078           2 :                     break;
    1079             :                 case SETOP_HASHED:
    1080           2 :                     pname = "HashSetOp";
    1081           2 :                     strategy = "Hashed";
    1082           2 :                     break;
    1083             :                 default:
    1084           0 :                     pname = "SetOp ???";
    1085           0 :                     strategy = "???";
    1086           0 :                     break;
    1087             :             }
    1088           4 :             break;
    1089             :         case T_LockRows:
    1090           3 :             pname = sname = "LockRows";
    1091           3 :             break;
    1092             :         case T_Limit:
    1093          52 :             pname = sname = "Limit";
    1094          52 :             break;
    1095             :         case T_Hash:
    1096          53 :             pname = sname = "Hash";
    1097          53 :             break;
    1098             :         default:
    1099           0 :             pname = sname = "???";
    1100           0 :             break;
    1101             :     }
    1102             : 
    1103        3384 :     ExplainOpenGroup("Plan",
    1104             :                      relationship ? NULL : "Plan",
    1105             :                      true, es);
    1106             : 
    1107        3384 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    1108             :     {
    1109        3382 :         if (plan_name)
    1110             :         {
    1111          83 :             appendStringInfoSpaces(es->str, es->indent * 2);
    1112          83 :             appendStringInfo(es->str, "%s\n", plan_name);
    1113          83 :             es->indent++;
    1114             :         }
    1115        3382 :         if (es->indent)
    1116             :         {
    1117        2265 :             appendStringInfoSpaces(es->str, es->indent * 2);
    1118        2265 :             appendStringInfoString(es->str, "->  ");
    1119        2265 :             es->indent += 2;
    1120             :         }
    1121        3382 :         if (plan->parallel_aware)
    1122          22 :             appendStringInfoString(es->str, "Parallel ");
    1123        3382 :         appendStringInfoString(es->str, pname);
    1124        3382 :         es->indent++;
    1125             :     }
    1126             :     else
    1127             :     {
    1128           2 :         ExplainPropertyText("Node Type", sname, es);
    1129           2 :         if (strategy)
    1130           0 :             ExplainPropertyText("Strategy", strategy, es);
    1131           2 :         if (partialmode)
    1132           0 :             ExplainPropertyText("Partial Mode", partialmode, es);
    1133           2 :         if (operation)
    1134           1 :             ExplainPropertyText("Operation", operation, es);
    1135           2 :         if (relationship)
    1136           1 :             ExplainPropertyText("Parent Relationship", relationship, es);
    1137           2 :         if (plan_name)
    1138           0 :             ExplainPropertyText("Subplan Name", plan_name, es);
    1139           2 :         if (custom_name)
    1140           0 :             ExplainPropertyText("Custom Plan Provider", custom_name, es);
    1141           2 :         ExplainPropertyBool("Parallel Aware", plan->parallel_aware, es);
    1142             :     }
    1143             : 
    1144        3384 :     switch (nodeTag(plan))
    1145             :     {
    1146             :         case T_SeqScan:
    1147             :         case T_SampleScan:
    1148             :         case T_BitmapHeapScan:
    1149             :         case T_TidScan:
    1150             :         case T_SubqueryScan:
    1151             :         case T_FunctionScan:
    1152             :         case T_TableFuncScan:
    1153             :         case T_ValuesScan:
    1154             :         case T_CteScan:
    1155             :         case T_WorkTableScan:
    1156        1246 :             ExplainScanTarget((Scan *) plan, es);
    1157        1246 :             break;
    1158             :         case T_ForeignScan:
    1159             :         case T_CustomScan:
    1160           0 :             if (((Scan *) plan)->scanrelid > 0)
    1161           0 :                 ExplainScanTarget((Scan *) plan, es);
    1162           0 :             break;
    1163             :         case T_IndexScan:
    1164             :             {
    1165         164 :                 IndexScan  *indexscan = (IndexScan *) plan;
    1166             : 
    1167         164 :                 ExplainIndexScanDetails(indexscan->indexid,
    1168             :                                         indexscan->indexorderdir,
    1169             :                                         es);
    1170         164 :                 ExplainScanTarget((Scan *) indexscan, es);
    1171             :             }
    1172         164 :             break;
    1173             :         case T_IndexOnlyScan:
    1174             :             {
    1175         160 :                 IndexOnlyScan *indexonlyscan = (IndexOnlyScan *) plan;
    1176             : 
    1177         160 :                 ExplainIndexScanDetails(indexonlyscan->indexid,
    1178             :                                         indexonlyscan->indexorderdir,
    1179             :                                         es);
    1180         160 :                 ExplainScanTarget((Scan *) indexonlyscan, es);
    1181             :             }
    1182         160 :             break;
    1183             :         case T_BitmapIndexScan:
    1184             :             {
    1185         327 :                 BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
    1186         327 :                 const char *indexname =
    1187         327 :                 explain_get_index_name(bitmapindexscan->indexid);
    1188             : 
    1189         327 :                 if (es->format == EXPLAIN_FORMAT_TEXT)
    1190         327 :                     appendStringInfo(es->str, " on %s", indexname);
    1191             :                 else
    1192           0 :                     ExplainPropertyText("Index Name", indexname, es);
    1193             :             }
    1194         327 :             break;
    1195             :         case T_ModifyTable:
    1196          61 :             ExplainModifyTarget((ModifyTable *) plan, es);
    1197          61 :             break;
    1198             :         case T_NestLoop:
    1199             :         case T_MergeJoin:
    1200             :         case T_HashJoin:
    1201             :             {
    1202             :                 const char *jointype;
    1203             : 
    1204         234 :                 switch (((Join *) plan)->jointype)
    1205             :                 {
    1206             :                     case JOIN_INNER:
    1207         161 :                         jointype = "Inner";
    1208         161 :                         break;
    1209             :                     case JOIN_LEFT:
    1210          50 :                         jointype = "Left";
    1211          50 :                         break;
    1212             :                     case JOIN_FULL:
    1213           5 :                         jointype = "Full";
    1214           5 :                         break;
    1215             :                     case JOIN_RIGHT:
    1216          12 :                         jointype = "Right";
    1217          12 :                         break;
    1218             :                     case JOIN_SEMI:
    1219           4 :                         jointype = "Semi";
    1220           4 :                         break;
    1221             :                     case JOIN_ANTI:
    1222           2 :                         jointype = "Anti";
    1223           2 :                         break;
    1224             :                     default:
    1225           0 :                         jointype = "???";
    1226           0 :                         break;
    1227             :                 }
    1228         234 :                 if (es->format == EXPLAIN_FORMAT_TEXT)
    1229             :                 {
    1230             :                     /*
    1231             :                      * For historical reasons, the join type is interpolated
    1232             :                      * into the node type name...
    1233             :                      */
    1234         234 :                     if (((Join *) plan)->jointype != JOIN_INNER)
    1235          73 :                         appendStringInfo(es->str, " %s Join", jointype);
    1236         161 :                     else if (!IsA(plan, NestLoop))
    1237          33 :                         appendStringInfoString(es->str, " Join");
    1238             :                 }
    1239             :                 else
    1240           0 :                     ExplainPropertyText("Join Type", jointype, es);
    1241             :             }
    1242         234 :             break;
    1243             :         case T_SetOp:
    1244             :             {
    1245             :                 const char *setopcmd;
    1246             : 
    1247           4 :                 switch (((SetOp *) plan)->cmd)
    1248             :                 {
    1249             :                     case SETOPCMD_INTERSECT:
    1250           2 :                         setopcmd = "Intersect";
    1251           2 :                         break;
    1252             :                     case SETOPCMD_INTERSECT_ALL:
    1253           0 :                         setopcmd = "Intersect All";
    1254           0 :                         break;
    1255             :                     case SETOPCMD_EXCEPT:
    1256           2 :                         setopcmd = "Except";
    1257           2 :                         break;
    1258             :                     case SETOPCMD_EXCEPT_ALL:
    1259           0 :                         setopcmd = "Except All";
    1260           0 :                         break;
    1261             :                     default:
    1262           0 :                         setopcmd = "???";
    1263           0 :                         break;
    1264             :                 }
    1265           4 :                 if (es->format == EXPLAIN_FORMAT_TEXT)
    1266           4 :                     appendStringInfo(es->str, " %s", setopcmd);
    1267             :                 else
    1268           0 :                     ExplainPropertyText("Command", setopcmd, es);
    1269             :             }
    1270           4 :             break;
    1271             :         default:
    1272        1188 :             break;
    1273             :     }
    1274             : 
    1275        3384 :     if (es->costs)
    1276             :     {
    1277        1247 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    1278             :         {
    1279        1247 :             appendStringInfo(es->str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
    1280             :                              plan->startup_cost, plan->total_cost,
    1281             :                              plan->plan_rows, plan->plan_width);
    1282             :         }
    1283             :         else
    1284             :         {
    1285           0 :             ExplainPropertyFloat("Startup Cost", plan->startup_cost, 2, es);
    1286           0 :             ExplainPropertyFloat("Total Cost", plan->total_cost, 2, es);
    1287           0 :             ExplainPropertyFloat("Plan Rows", plan->plan_rows, 0, es);
    1288           0 :             ExplainPropertyInteger("Plan Width", plan->plan_width, es);
    1289             :         }
    1290             :     }
    1291             : 
    1292             :     /*
    1293             :      * We have to forcibly clean up the instrumentation state because we
    1294             :      * haven't done ExecutorEnd yet.  This is pretty grotty ...
    1295             :      *
    1296             :      * Note: contrib/auto_explain could cause instrumentation to be set up
    1297             :      * even though we didn't ask for it here.  Be careful not to print any
    1298             :      * instrumentation results the user didn't ask for.  But we do the
    1299             :      * InstrEndLoop call anyway, if possible, to reduce the number of cases
    1300             :      * auto_explain has to contend with.
    1301             :      */
    1302        3384 :     if (planstate->instrument)
    1303          12 :         InstrEndLoop(planstate->instrument);
    1304             : 
    1305        3396 :     if (es->analyze &&
    1306          24 :         planstate->instrument && planstate->instrument->nloops > 0)
    1307          12 :     {
    1308          12 :         double      nloops = planstate->instrument->nloops;
    1309          12 :         double      startup_sec = 1000.0 * planstate->instrument->startup / nloops;
    1310          12 :         double      total_sec = 1000.0 * planstate->instrument->total / nloops;
    1311          12 :         double      rows = planstate->instrument->ntuples / nloops;
    1312             : 
    1313          12 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    1314             :         {
    1315          12 :             if (es->timing)
    1316           1 :                 appendStringInfo(es->str,
    1317             :                                  " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
    1318             :                                  startup_sec, total_sec, rows, nloops);
    1319             :             else
    1320          11 :                 appendStringInfo(es->str,
    1321             :                                  " (actual rows=%.0f loops=%.0f)",
    1322             :                                  rows, nloops);
    1323             :         }
    1324             :         else
    1325             :         {
    1326           0 :             if (es->timing)
    1327             :             {
    1328           0 :                 ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
    1329           0 :                 ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
    1330             :             }
    1331           0 :             ExplainPropertyFloat("Actual Rows", rows, 0, es);
    1332           0 :             ExplainPropertyFloat("Actual Loops", nloops, 0, es);
    1333             :         }
    1334             :     }
    1335        3372 :     else if (es->analyze)
    1336             :     {
    1337           0 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    1338           0 :             appendStringInfoString(es->str, " (never executed)");
    1339             :         else
    1340             :         {
    1341           0 :             if (es->timing)
    1342             :             {
    1343           0 :                 ExplainPropertyFloat("Actual Startup Time", 0.0, 3, es);
    1344           0 :                 ExplainPropertyFloat("Actual Total Time", 0.0, 3, es);
    1345             :             }
    1346           0 :             ExplainPropertyFloat("Actual Rows", 0.0, 0, es);
    1347           0 :             ExplainPropertyFloat("Actual Loops", 0.0, 0, es);
    1348             :         }
    1349             :     }
    1350             : 
    1351             :     /* in text format, first line ends here */
    1352        3384 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    1353        3382 :         appendStringInfoChar(es->str, '\n');
    1354             : 
    1355             :     /* target list */
    1356        3384 :     if (es->verbose)
    1357         435 :         show_plan_tlist(planstate, ancestors, es);
    1358             : 
    1359             :     /* unique join */
    1360        3384 :     switch (nodeTag(plan))
    1361             :     {
    1362             :         case T_NestLoop:
    1363             :         case T_MergeJoin:
    1364             :         case T_HashJoin:
    1365             :             /* try not to be too chatty about this in text mode */
    1366         468 :             if (es->format != EXPLAIN_FORMAT_TEXT ||
    1367         323 :                 (es->verbose && ((Join *) plan)->inner_unique))
    1368           9 :                 ExplainPropertyBool("Inner Unique",
    1369           9 :                                     ((Join *) plan)->inner_unique,
    1370             :                                     es);
    1371         234 :             break;
    1372             :         default:
    1373        3150 :             break;
    1374             :     }
    1375             : 
    1376             :     /* quals, sort keys, etc */
    1377        3384 :     switch (nodeTag(plan))
    1378             :     {
    1379             :         case T_IndexScan:
    1380         164 :             show_scan_qual(((IndexScan *) plan)->indexqualorig,
    1381             :                            "Index Cond", planstate, ancestors, es);
    1382         164 :             if (((IndexScan *) plan)->indexqualorig)
    1383         135 :                 show_instrumentation_count("Rows Removed by Index Recheck", 2,
    1384             :                                            planstate, es);
    1385         164 :             show_scan_qual(((IndexScan *) plan)->indexorderbyorig,
    1386             :                            "Order By", planstate, ancestors, es);
    1387         164 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1388         164 :             if (plan->qual)
    1389          33 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1390             :                                            planstate, es);
    1391         164 :             break;
    1392             :         case T_IndexOnlyScan:
    1393         160 :             show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
    1394             :                            "Index Cond", planstate, ancestors, es);
    1395         160 :             if (((IndexOnlyScan *) plan)->indexqual)
    1396         128 :                 show_instrumentation_count("Rows Removed by Index Recheck", 2,
    1397             :                                            planstate, es);
    1398         160 :             show_scan_qual(((IndexOnlyScan *) plan)->indexorderby,
    1399             :                            "Order By", planstate, ancestors, es);
    1400         160 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1401         160 :             if (plan->qual)
    1402           4 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1403             :                                            planstate, es);
    1404         160 :             if (es->analyze)
    1405           0 :                 ExplainPropertyLong("Heap Fetches",
    1406             :                                     ((IndexOnlyScanState *) planstate)->ioss_HeapFetches, es);
    1407         160 :             break;
    1408             :         case T_BitmapIndexScan:
    1409         327 :             show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
    1410             :                            "Index Cond", planstate, ancestors, es);
    1411         327 :             break;
    1412             :         case T_BitmapHeapScan:
    1413         314 :             show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
    1414             :                            "Recheck Cond", planstate, ancestors, es);
    1415         314 :             if (((BitmapHeapScan *) plan)->bitmapqualorig)
    1416         312 :                 show_instrumentation_count("Rows Removed by Index Recheck", 2,
    1417             :                                            planstate, es);
    1418         314 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1419         314 :             if (plan->qual)
    1420           5 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1421             :                                            planstate, es);
    1422         314 :             if (es->analyze)
    1423           0 :                 show_tidbitmap_info((BitmapHeapScanState *) planstate, es);
    1424         314 :             break;
    1425             :         case T_SampleScan:
    1426           9 :             show_tablesample(((SampleScan *) plan)->tablesample,
    1427             :                              planstate, ancestors, es);
    1428             :             /* FALL THRU to print additional fields the same as SeqScan */
    1429             :         case T_SeqScan:
    1430             :         case T_ValuesScan:
    1431             :         case T_CteScan:
    1432             :         case T_NamedTuplestoreScan:
    1433             :         case T_WorkTableScan:
    1434             :         case T_SubqueryScan:
    1435         901 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1436         901 :             if (plan->qual)
    1437         547 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1438             :                                            planstate, es);
    1439         901 :             break;
    1440             :         case T_Gather:
    1441             :             {
    1442          14 :                 Gather     *gather = (Gather *) plan;
    1443             : 
    1444          14 :                 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1445          14 :                 if (plan->qual)
    1446           0 :                     show_instrumentation_count("Rows Removed by Filter", 1,
    1447             :                                                planstate, es);
    1448          14 :                 ExplainPropertyInteger("Workers Planned",
    1449             :                                        gather->num_workers, es);
    1450          14 :                 if (es->analyze)
    1451             :                 {
    1452             :                     int         nworkers;
    1453             : 
    1454           1 :                     nworkers = ((GatherState *) planstate)->nworkers_launched;
    1455           1 :                     ExplainPropertyInteger("Workers Launched",
    1456             :                                            nworkers, es);
    1457             :                 }
    1458          14 :                 if (gather->single_copy || es->format != EXPLAIN_FORMAT_TEXT)
    1459           1 :                     ExplainPropertyBool("Single Copy", gather->single_copy, es);
    1460             :             }
    1461          14 :             break;
    1462             :         case T_GatherMerge:
    1463             :             {
    1464           4 :                 GatherMerge *gm = (GatherMerge *) plan;
    1465             : 
    1466           4 :                 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1467           4 :                 if (plan->qual)
    1468           0 :                     show_instrumentation_count("Rows Removed by Filter", 1,
    1469             :                                                planstate, es);
    1470           4 :                 ExplainPropertyInteger("Workers Planned",
    1471             :                                        gm->num_workers, es);
    1472           4 :                 if (es->analyze)
    1473             :                 {
    1474             :                     int         nworkers;
    1475             : 
    1476           0 :                     nworkers = ((GatherMergeState *) planstate)->nworkers_launched;
    1477           0 :                     ExplainPropertyInteger("Workers Launched",
    1478             :                                            nworkers, es);
    1479             :                 }
    1480             :             }
    1481           4 :             break;
    1482             :         case T_FunctionScan:
    1483          23 :             if (es->verbose)
    1484             :             {
    1485           7 :                 List       *fexprs = NIL;
    1486             :                 ListCell   *lc;
    1487             : 
    1488          14 :                 foreach(lc, ((FunctionScan *) plan)->functions)
    1489             :                 {
    1490           7 :                     RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
    1491             : 
    1492           7 :                     fexprs = lappend(fexprs, rtfunc->funcexpr);
    1493             :                 }
    1494             :                 /* We rely on show_expression to insert commas as needed */
    1495           7 :                 show_expression((Node *) fexprs,
    1496             :                                 "Function Call", planstate, ancestors,
    1497           7 :                                 es->verbose, es);
    1498             :             }
    1499          23 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1500          23 :             if (plan->qual)
    1501           2 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1502             :                                            planstate, es);
    1503          23 :             break;
    1504             :         case T_TableFuncScan:
    1505           5 :             if (es->verbose)
    1506             :             {
    1507           4 :                 TableFunc  *tablefunc = ((TableFuncScan *) plan)->tablefunc;
    1508             : 
    1509           4 :                 show_expression((Node *) tablefunc,
    1510             :                                 "Table Function Call", planstate, ancestors,
    1511           4 :                                 es->verbose, es);
    1512             :             }
    1513           5 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1514           5 :             if (plan->qual)
    1515           2 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1516             :                                            planstate, es);
    1517           5 :             break;
    1518             :         case T_TidScan:
    1519             :             {
    1520             :                 /*
    1521             :                  * The tidquals list has OR semantics, so be sure to show it
    1522             :                  * as an OR condition.
    1523             :                  */
    1524           7 :                 List       *tidquals = ((TidScan *) plan)->tidquals;
    1525             : 
    1526           7 :                 if (list_length(tidquals) > 1)
    1527           1 :                     tidquals = list_make1(make_orclause(tidquals));
    1528           7 :                 show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
    1529           7 :                 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1530           7 :                 if (plan->qual)
    1531           2 :                     show_instrumentation_count("Rows Removed by Filter", 1,
    1532             :                                                planstate, es);
    1533             :             }
    1534           7 :             break;
    1535             :         case T_ForeignScan:
    1536           0 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1537           0 :             if (plan->qual)
    1538           0 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1539             :                                            planstate, es);
    1540           0 :             show_foreignscan_info((ForeignScanState *) planstate, es);
    1541           0 :             break;
    1542             :         case T_CustomScan:
    1543             :             {
    1544           0 :                 CustomScanState *css = (CustomScanState *) planstate;
    1545             : 
    1546           0 :                 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1547           0 :                 if (plan->qual)
    1548           0 :                     show_instrumentation_count("Rows Removed by Filter", 1,
    1549             :                                                planstate, es);
    1550           0 :                 if (css->methods->ExplainCustomScan)
    1551           0 :                     css->methods->ExplainCustomScan(css, ancestors, es);
    1552             :             }
    1553           0 :             break;
    1554             :         case T_NestLoop:
    1555         167 :             show_upper_qual(((NestLoop *) plan)->join.joinqual,
    1556             :                             "Join Filter", planstate, ancestors, es);
    1557         167 :             if (((NestLoop *) plan)->join.joinqual)
    1558          48 :                 show_instrumentation_count("Rows Removed by Join Filter", 1,
    1559             :                                            planstate, es);
    1560         167 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1561         167 :             if (plan->qual)
    1562           4 :                 show_instrumentation_count("Rows Removed by Filter", 2,
    1563             :                                            planstate, es);
    1564         167 :             break;
    1565             :         case T_MergeJoin:
    1566          14 :             show_upper_qual(((MergeJoin *) plan)->mergeclauses,
    1567             :                             "Merge Cond", planstate, ancestors, es);
    1568          14 :             show_upper_qual(((MergeJoin *) plan)->join.joinqual,
    1569             :                             "Join Filter", planstate, ancestors, es);
    1570          14 :             if (((MergeJoin *) plan)->join.joinqual)
    1571           1 :                 show_instrumentation_count("Rows Removed by Join Filter", 1,
    1572             :                                            planstate, es);
    1573          14 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1574          14 :             if (plan->qual)
    1575           1 :                 show_instrumentation_count("Rows Removed by Filter", 2,
    1576             :                                            planstate, es);
    1577          14 :             break;
    1578             :         case T_HashJoin:
    1579          53 :             show_upper_qual(((HashJoin *) plan)->hashclauses,
    1580             :                             "Hash Cond", planstate, ancestors, es);
    1581          53 :             show_upper_qual(((HashJoin *) plan)->join.joinqual,
    1582             :                             "Join Filter", planstate, ancestors, es);
    1583          53 :             if (((HashJoin *) plan)->join.joinqual)
    1584           0 :                 show_instrumentation_count("Rows Removed by Join Filter", 1,
    1585             :                                            planstate, es);
    1586          53 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1587          53 :             if (plan->qual)
    1588           1 :                 show_instrumentation_count("Rows Removed by Filter", 2,
    1589             :                                            planstate, es);
    1590          53 :             break;
    1591             :         case T_Agg:
    1592         674 :             show_agg_keys(castNode(AggState, planstate), ancestors, es);
    1593         674 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1594         674 :             if (plan->qual)
    1595           2 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1596             :                                            planstate, es);
    1597         674 :             break;
    1598             :         case T_Group:
    1599           2 :             show_group_keys(castNode(GroupState, planstate), ancestors, es);
    1600           2 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1601           2 :             if (plan->qual)
    1602           0 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1603             :                                            planstate, es);
    1604           2 :             break;
    1605             :         case T_Sort:
    1606          98 :             show_sort_keys(castNode(SortState, planstate), ancestors, es);
    1607          98 :             show_sort_info(castNode(SortState, planstate), es);
    1608          98 :             break;
    1609             :         case T_MergeAppend:
    1610          22 :             show_merge_append_keys(castNode(MergeAppendState, planstate),
    1611             :                                    ancestors, es);
    1612          22 :             break;
    1613             :         case T_Result:
    1614         108 :             show_upper_qual((List *) ((Result *) plan)->resconstantqual,
    1615             :                             "One-Time Filter", planstate, ancestors, es);
    1616         108 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1617         108 :             if (plan->qual)
    1618           0 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1619             :                                            planstate, es);
    1620         108 :             break;
    1621             :         case T_ModifyTable:
    1622          61 :             show_modifytable_info(castNode(ModifyTableState, planstate), ancestors,
    1623             :                                   es);
    1624          61 :             break;
    1625             :         case T_Hash:
    1626          53 :             show_hash_info(castNode(HashState, planstate), es);
    1627          53 :             break;
    1628             :         default:
    1629         213 :             break;
    1630             :     }
    1631             : 
    1632             :     /* Show buffer usage */
    1633        3384 :     if (es->buffers && planstate->instrument)
    1634           0 :         show_buffer_usage(es, &planstate->instrument->bufusage);
    1635             : 
    1636             :     /* Show worker detail */
    1637        3384 :     if (es->analyze && es->verbose && planstate->worker_instrument)
    1638             :     {
    1639           0 :         WorkerInstrumentation *w = planstate->worker_instrument;
    1640           0 :         bool        opened_group = false;
    1641             :         int         n;
    1642             : 
    1643           0 :         for (n = 0; n < w->num_workers; ++n)
    1644             :         {
    1645           0 :             Instrumentation *instrument = &w->instrument[n];
    1646           0 :             double      nloops = instrument->nloops;
    1647             :             double      startup_sec;
    1648             :             double      total_sec;
    1649             :             double      rows;
    1650             : 
    1651           0 :             if (nloops <= 0)
    1652           0 :                 continue;
    1653           0 :             startup_sec = 1000.0 * instrument->startup / nloops;
    1654           0 :             total_sec = 1000.0 * instrument->total / nloops;
    1655           0 :             rows = instrument->ntuples / nloops;
    1656             : 
    1657           0 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    1658             :             {
    1659           0 :                 appendStringInfoSpaces(es->str, es->indent * 2);
    1660           0 :                 appendStringInfo(es->str, "Worker %d: ", n);
    1661           0 :                 if (es->timing)
    1662           0 :                     appendStringInfo(es->str,
    1663             :                                      "actual time=%.3f..%.3f rows=%.0f loops=%.0f\n",
    1664             :                                      startup_sec, total_sec, rows, nloops);
    1665             :                 else
    1666           0 :                     appendStringInfo(es->str,
    1667             :                                      "actual rows=%.0f loops=%.0f\n",
    1668             :                                      rows, nloops);
    1669           0 :                 es->indent++;
    1670           0 :                 if (es->buffers)
    1671           0 :                     show_buffer_usage(es, &instrument->bufusage);
    1672           0 :                 es->indent--;
    1673             :             }
    1674             :             else
    1675             :             {
    1676           0 :                 if (!opened_group)
    1677             :                 {
    1678           0 :                     ExplainOpenGroup("Workers", "Workers", false, es);
    1679           0 :                     opened_group = true;
    1680             :                 }
    1681           0 :                 ExplainOpenGroup("Worker", NULL, true, es);
    1682           0 :                 ExplainPropertyInteger("Worker Number", n, es);
    1683             : 
    1684           0 :                 if (es->timing)
    1685             :                 {
    1686           0 :                     ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
    1687           0 :                     ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
    1688             :                 }
    1689           0 :                 ExplainPropertyFloat("Actual Rows", rows, 0, es);
    1690           0 :                 ExplainPropertyFloat("Actual Loops", nloops, 0, es);
    1691             : 
    1692           0 :                 if (es->buffers)
    1693           0 :                     show_buffer_usage(es, &instrument->bufusage);
    1694             : 
    1695           0 :                 ExplainCloseGroup("Worker", NULL, true, es);
    1696             :             }
    1697             :         }
    1698             : 
    1699           0 :         if (opened_group)
    1700           0 :             ExplainCloseGroup("Workers", "Workers", false, es);
    1701             :     }
    1702             : 
    1703             :     /* Get ready to display the child plans */
    1704       10102 :     haschildren = planstate->initPlan ||
    1705        5159 :         outerPlanState(planstate) ||
    1706        3650 :         innerPlanState(planstate) ||
    1707        3593 :         IsA(plan, ModifyTable) ||
    1708        3461 :         IsA(plan, Append) ||
    1709        3364 :         IsA(plan, MergeAppend) ||
    1710        3341 :         IsA(plan, BitmapAnd) ||
    1711        3330 :         IsA(plan, BitmapOr) ||
    1712        3284 :         IsA(plan, SubqueryScan) ||
    1713        1624 :         (IsA(planstate, CustomScanState) &&
    1714        5008 :          ((CustomScanState *) planstate)->custom_ps != NIL) ||
    1715        1624 :         planstate->subPlan;
    1716        3384 :     if (haschildren)
    1717             :     {
    1718        1784 :         ExplainOpenGroup("Plans", "Plans", false, es);
    1719             :         /* Pass current PlanState as head of ancestors list for children */
    1720        1784 :         ancestors = lcons(planstate, ancestors);
    1721             :     }
    1722             : 
    1723             :     /* initPlan-s */
    1724        3384 :     if (planstate->initPlan)
    1725          50 :         ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es);
    1726             : 
    1727             :     /* lefttree */
    1728        3384 :     if (outerPlanState(planstate))
    1729        1528 :         ExplainNode(outerPlanState(planstate), ancestors,
    1730             :                     "Outer", NULL, es);
    1731             : 
    1732             :     /* righttree */
    1733        3384 :     if (innerPlanState(planstate))
    1734         234 :         ExplainNode(innerPlanState(planstate), ancestors,
    1735             :                     "Inner", NULL, es);
    1736             : 
    1737             :     /* special child plans */
    1738        3384 :     switch (nodeTag(plan))
    1739             :     {
    1740             :         case T_ModifyTable:
    1741          61 :             ExplainMemberNodes(((ModifyTable *) plan)->plans,
    1742             :                                ((ModifyTableState *) planstate)->mt_plans,
    1743             :                                ancestors, es);
    1744          61 :             break;
    1745             :         case T_Append:
    1746          80 :             ExplainMemberNodes(((Append *) plan)->appendplans,
    1747             :                                ((AppendState *) planstate)->appendplans,
    1748             :                                ancestors, es);
    1749          80 :             break;
    1750             :         case T_MergeAppend:
    1751          22 :             ExplainMemberNodes(((MergeAppend *) plan)->mergeplans,
    1752             :                                ((MergeAppendState *) planstate)->mergeplans,
    1753             :                                ancestors, es);
    1754          22 :             break;
    1755             :         case T_BitmapAnd:
    1756           1 :             ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
    1757             :                                ((BitmapAndState *) planstate)->bitmapplans,
    1758             :                                ancestors, es);
    1759           1 :             break;
    1760             :         case T_BitmapOr:
    1761          10 :             ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
    1762             :                                ((BitmapOrState *) planstate)->bitmapplans,
    1763             :                                ancestors, es);
    1764          10 :             break;
    1765             :         case T_SubqueryScan:
    1766          36 :             ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,
    1767             :                         "Subquery", NULL, es);
    1768          36 :             break;
    1769             :         case T_CustomScan:
    1770           0 :             ExplainCustomChildren((CustomScanState *) planstate,
    1771             :                                   ancestors, es);
    1772           0 :             break;
    1773             :         default:
    1774        3174 :             break;
    1775             :     }
    1776             : 
    1777             :     /* subPlan-s */
    1778        3384 :     if (planstate->subPlan)
    1779          28 :         ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es);
    1780             : 
    1781             :     /* end of child plans */
    1782        3384 :     if (haschildren)
    1783             :     {
    1784        1784 :         ancestors = list_delete_first(ancestors);
    1785        1784 :         ExplainCloseGroup("Plans", "Plans", false, es);
    1786             :     }
    1787             : 
    1788             :     /* in text format, undo whatever indentation we added */
    1789        3384 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    1790        3382 :         es->indent = save_indent;
    1791             : 
    1792        3384 :     ExplainCloseGroup("Plan",
    1793             :                       relationship ? NULL : "Plan",
    1794             :                       true, es);
    1795        3384 : }
    1796             : 
    1797             : /*
    1798             :  * Show the targetlist of a plan node
    1799             :  */
    1800             : static void
    1801         435 : show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
    1802             : {
    1803         435 :     Plan       *plan = planstate->plan;
    1804             :     List       *context;
    1805         435 :     List       *result = NIL;
    1806             :     bool        useprefix;
    1807             :     ListCell   *lc;
    1808             : 
    1809             :     /* No work if empty tlist (this occurs eg in bitmap indexscans) */
    1810         435 :     if (plan->targetlist == NIL)
    1811          22 :         return;
    1812             :     /* The tlist of an Append isn't real helpful, so suppress it */
    1813         413 :     if (IsA(plan, Append))
    1814           6 :         return;
    1815             :     /* Likewise for MergeAppend and RecursiveUnion */
    1816         407 :     if (IsA(plan, MergeAppend))
    1817           2 :         return;
    1818         405 :     if (IsA(plan, RecursiveUnion))
    1819           0 :         return;
    1820             : 
    1821             :     /*
    1822             :      * Likewise for ForeignScan that executes a direct INSERT/UPDATE/DELETE
    1823             :      *
    1824             :      * Note: the tlist for a ForeignScan that executes a direct INSERT/UPDATE
    1825             :      * might contain subplan output expressions that are confusing in this
    1826             :      * context.  The tlist for a ForeignScan that executes a direct UPDATE/
    1827             :      * DELETE always contains "junk" target columns to identify the exact row
    1828             :      * to update or delete, which would be confusing in this context.  So, we
    1829             :      * suppress it in all the cases.
    1830             :      */
    1831         405 :     if (IsA(plan, ForeignScan) &&
    1832           0 :         ((ForeignScan *) plan)->operation != CMD_SELECT)
    1833           0 :         return;
    1834             : 
    1835             :     /* Set up deparsing context */
    1836         405 :     context = set_deparse_context_planstate(es->deparse_cxt,
    1837             :                                             (Node *) planstate,
    1838             :                                             ancestors);
    1839         405 :     useprefix = list_length(es->rtable) > 1;
    1840             : 
    1841             :     /* Deparse each result column (we now include resjunk ones) */
    1842        1272 :     foreach(lc, plan->targetlist)
    1843             :     {
    1844         867 :         TargetEntry *tle = (TargetEntry *) lfirst(lc);
    1845             : 
    1846         867 :         result = lappend(result,
    1847         867 :                          deparse_expression((Node *) tle->expr, context,
    1848             :                                             useprefix, false));
    1849             :     }
    1850             : 
    1851             :     /* Print results */
    1852         405 :     ExplainPropertyList("Output", result, es);
    1853             : }
    1854             : 
    1855             : /*
    1856             :  * Show a generic expression
    1857             :  */
    1858             : static void
    1859        1669 : show_expression(Node *node, const char *qlabel,
    1860             :                 PlanState *planstate, List *ancestors,
    1861             :                 bool useprefix, ExplainState *es)
    1862             : {
    1863             :     List       *context;
    1864             :     char       *exprstr;
    1865             : 
    1866             :     /* Set up deparsing context */
    1867        1669 :     context = set_deparse_context_planstate(es->deparse_cxt,
    1868             :                                             (Node *) planstate,
    1869             :                                             ancestors);
    1870             : 
    1871             :     /* Deparse the expression */
    1872        1669 :     exprstr = deparse_expression(node, context, useprefix, false);
    1873             : 
    1874             :     /* And add to es->str */
    1875        1669 :     ExplainPropertyText(qlabel, exprstr, es);
    1876        1669 : }
    1877             : 
    1878             : /*
    1879             :  * Show a qualifier expression (which is a List with implicit AND semantics)
    1880             :  */
    1881             : static void
    1882        4322 : show_qual(List *qual, const char *qlabel,
    1883             :           PlanState *planstate, List *ancestors,
    1884             :           bool useprefix, ExplainState *es)
    1885             : {
    1886             :     Node       *node;
    1887             : 
    1888             :     /* No work if empty qual */
    1889        4322 :     if (qual == NIL)
    1890        6986 :         return;
    1891             : 
    1892             :     /* Convert AND list to explicit AND */
    1893        1658 :     node = (Node *) make_ands_explicit(qual);
    1894             : 
    1895             :     /* And show it */
    1896        1658 :     show_expression(node, qlabel, planstate, ancestors, useprefix, es);
    1897             : }
    1898             : 
    1899             : /*
    1900             :  * Show a qualifier expression for a scan plan node
    1901             :  */
    1902             : static void
    1903        2888 : show_scan_qual(List *qual, const char *qlabel,
    1904             :                PlanState *planstate, List *ancestors,
    1905             :                ExplainState *es)
    1906             : {
    1907             :     bool        useprefix;
    1908             : 
    1909        2888 :     useprefix = (IsA(planstate->plan, SubqueryScan) ||es->verbose);
    1910        2888 :     show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
    1911        2888 : }
    1912             : 
    1913             : /*
    1914             :  * Show a qualifier expression for an upper-level plan node
    1915             :  */
    1916             : static void
    1917        1434 : show_upper_qual(List *qual, const char *qlabel,
    1918             :                 PlanState *planstate, List *ancestors,
    1919             :                 ExplainState *es)
    1920             : {
    1921             :     bool        useprefix;
    1922             : 
    1923        1434 :     useprefix = (list_length(es->rtable) > 1 || es->verbose);
    1924        1434 :     show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
    1925        1434 : }
    1926             : 
    1927             : /*
    1928             :  * Show the sort keys for a Sort node.
    1929             :  */
    1930             : static void
    1931          98 : show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
    1932             : {
    1933          98 :     Sort       *plan = (Sort *) sortstate->ss.ps.plan;
    1934             : 
    1935          98 :     show_sort_group_keys((PlanState *) sortstate, "Sort Key",
    1936             :                          plan->numCols, plan->sortColIdx,
    1937             :                          plan->sortOperators, plan->collations,
    1938             :                          plan->nullsFirst,
    1939             :                          ancestors, es);
    1940          98 : }
    1941             : 
    1942             : /*
    1943             :  * Likewise, for a MergeAppend node.
    1944             :  */
    1945             : static void
    1946          22 : show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
    1947             :                        ExplainState *es)
    1948             : {
    1949          22 :     MergeAppend *plan = (MergeAppend *) mstate->ps.plan;
    1950             : 
    1951          22 :     show_sort_group_keys((PlanState *) mstate, "Sort Key",
    1952             :                          plan->numCols, plan->sortColIdx,
    1953             :                          plan->sortOperators, plan->collations,
    1954             :                          plan->nullsFirst,
    1955             :                          ancestors, es);
    1956          22 : }
    1957             : 
    1958             : /*
    1959             :  * Show the grouping keys for an Agg node.
    1960             :  */
    1961             : static void
    1962         674 : show_agg_keys(AggState *astate, List *ancestors,
    1963             :               ExplainState *es)
    1964             : {
    1965         674 :     Agg        *plan = (Agg *) astate->ss.ps.plan;
    1966             : 
    1967         674 :     if (plan->numCols > 0 || plan->groupingSets)
    1968             :     {
    1969             :         /* The key columns refer to the tlist of the child plan */
    1970          67 :         ancestors = lcons(astate, ancestors);
    1971             : 
    1972          67 :         if (plan->groupingSets)
    1973          18 :             show_grouping_sets(outerPlanState(astate), plan, ancestors, es);
    1974             :         else
    1975          49 :             show_sort_group_keys(outerPlanState(astate), "Group Key",
    1976             :                                  plan->numCols, plan->grpColIdx,
    1977             :                                  NULL, NULL, NULL,
    1978             :                                  ancestors, es);
    1979             : 
    1980          67 :         ancestors = list_delete_first(ancestors);
    1981             :     }
    1982         674 : }
    1983             : 
    1984             : static void
    1985          18 : show_grouping_sets(PlanState *planstate, Agg *agg,
    1986             :                    List *ancestors, ExplainState *es)
    1987             : {
    1988             :     List       *context;
    1989             :     bool        useprefix;
    1990             :     ListCell   *lc;
    1991             : 
    1992             :     /* Set up deparsing context */
    1993          18 :     context = set_deparse_context_planstate(es->deparse_cxt,
    1994             :                                             (Node *) planstate,
    1995             :                                             ancestors);
    1996          18 :     useprefix = (list_length(es->rtable) > 1 || es->verbose);
    1997             : 
    1998          18 :     ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es);
    1999             : 
    2000          18 :     show_grouping_set_keys(planstate, agg, NULL,
    2001             :                            context, useprefix, ancestors, es);
    2002             : 
    2003          53 :     foreach(lc, agg->chain)
    2004             :     {
    2005          35 :         Agg        *aggnode = lfirst(lc);
    2006          35 :         Sort       *sortnode = (Sort *) aggnode->plan.lefttree;
    2007             : 
    2008          35 :         show_grouping_set_keys(planstate, aggnode, sortnode,
    2009             :                                context, useprefix, ancestors, es);
    2010             :     }
    2011             : 
    2012          18 :     ExplainCloseGroup("Grouping Sets", "Grouping Sets", false, es);
    2013          18 : }
    2014             : 
    2015             : static void
    2016          53 : show_grouping_set_keys(PlanState *planstate,
    2017             :                        Agg *aggnode, Sort *sortnode,
    2018             :                        List *context, bool useprefix,
    2019             :                        List *ancestors, ExplainState *es)
    2020             : {
    2021          53 :     Plan       *plan = planstate->plan;
    2022             :     char       *exprstr;
    2023             :     ListCell   *lc;
    2024          53 :     List       *gsets = aggnode->groupingSets;
    2025          53 :     AttrNumber *keycols = aggnode->grpColIdx;
    2026             :     const char *keyname;
    2027             :     const char *keysetname;
    2028             : 
    2029          53 :     if (aggnode->aggstrategy == AGG_HASHED || aggnode->aggstrategy == AGG_MIXED)
    2030             :     {
    2031          36 :         keyname = "Hash Key";
    2032          36 :         keysetname = "Hash Keys";
    2033             :     }
    2034             :     else
    2035             :     {
    2036          17 :         keyname = "Group Key";
    2037          17 :         keysetname = "Group Keys";
    2038             :     }
    2039             : 
    2040          53 :     ExplainOpenGroup("Grouping Set", NULL, true, es);
    2041             : 
    2042          53 :     if (sortnode)
    2043             :     {
    2044           4 :         show_sort_group_keys(planstate, "Sort Key",
    2045             :                              sortnode->numCols, sortnode->sortColIdx,
    2046             :                              sortnode->sortOperators, sortnode->collations,
    2047             :                              sortnode->nullsFirst,
    2048             :                              ancestors, es);
    2049           4 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    2050           4 :             es->indent++;
    2051             :     }
    2052             : 
    2053          53 :     ExplainOpenGroup(keysetname, keysetname, false, es);
    2054             : 
    2055         113 :     foreach(lc, gsets)
    2056             :     {
    2057          60 :         List       *result = NIL;
    2058             :         ListCell   *lc2;
    2059             : 
    2060         121 :         foreach(lc2, (List *) lfirst(lc))
    2061             :         {
    2062          61 :             Index       i = lfirst_int(lc2);
    2063          61 :             AttrNumber  keyresno = keycols[i];
    2064          61 :             TargetEntry *target = get_tle_by_resno(plan->targetlist,
    2065             :                                                    keyresno);
    2066             : 
    2067          61 :             if (!target)
    2068           0 :                 elog(ERROR, "no tlist entry for key %d", keyresno);
    2069             :             /* Deparse the expression, showing any top-level cast */
    2070          61 :             exprstr = deparse_expression((Node *) target->expr, context,
    2071             :                                          useprefix, true);
    2072             : 
    2073          61 :             result = lappend(result, exprstr);
    2074             :         }
    2075             : 
    2076          60 :         if (!result && es->format == EXPLAIN_FORMAT_TEXT)
    2077          12 :             ExplainPropertyText(keyname, "()", es);
    2078             :         else
    2079          48 :             ExplainPropertyListNested(keyname, result, es);
    2080             :     }
    2081             : 
    2082          53 :     ExplainCloseGroup(keysetname, keysetname, false, es);
    2083             : 
    2084          53 :     if (sortnode && es->format == EXPLAIN_FORMAT_TEXT)
    2085           4 :         es->indent--;
    2086             : 
    2087          53 :     ExplainCloseGroup("Grouping Set", NULL, true, es);
    2088          53 : }
    2089             : 
    2090             : /*
    2091             :  * Show the grouping keys for a Group node.
    2092             :  */
    2093             : static void
    2094           2 : show_group_keys(GroupState *gstate, List *ancestors,
    2095             :                 ExplainState *es)
    2096             : {
    2097           2 :     Group      *plan = (Group *) gstate->ss.ps.plan;
    2098             : 
    2099             :     /* The key columns refer to the tlist of the child plan */
    2100           2 :     ancestors = lcons(gstate, ancestors);
    2101           2 :     show_sort_group_keys(outerPlanState(gstate), "Group Key",
    2102             :                          plan->numCols, plan->grpColIdx,
    2103             :                          NULL, NULL, NULL,
    2104             :                          ancestors, es);
    2105           2 :     ancestors = list_delete_first(ancestors);
    2106           2 : }
    2107             : 
    2108             : /*
    2109             :  * Common code to show sort/group keys, which are represented in plan nodes
    2110             :  * as arrays of targetlist indexes.  If it's a sort key rather than a group
    2111             :  * key, also pass sort operators/collations/nullsFirst arrays.
    2112             :  */
    2113             : static void
    2114         175 : show_sort_group_keys(PlanState *planstate, const char *qlabel,
    2115             :                      int nkeys, AttrNumber *keycols,
    2116             :                      Oid *sortOperators, Oid *collations, bool *nullsFirst,
    2117             :                      List *ancestors, ExplainState *es)
    2118             : {
    2119         175 :     Plan       *plan = planstate->plan;
    2120             :     List       *context;
    2121         175 :     List       *result = NIL;
    2122             :     StringInfoData sortkeybuf;
    2123             :     bool        useprefix;
    2124             :     int         keyno;
    2125             : 
    2126         175 :     if (nkeys <= 0)
    2127         175 :         return;
    2128             : 
    2129         175 :     initStringInfo(&sortkeybuf);
    2130             : 
    2131             :     /* Set up deparsing context */
    2132         175 :     context = set_deparse_context_planstate(es->deparse_cxt,
    2133             :                                             (Node *) planstate,
    2134             :                                             ancestors);
    2135         175 :     useprefix = (list_length(es->rtable) > 1 || es->verbose);
    2136             : 
    2137         456 :     for (keyno = 0; keyno < nkeys; keyno++)
    2138             :     {
    2139             :         /* find key expression in tlist */
    2140         281 :         AttrNumber  keyresno = keycols[keyno];
    2141         281 :         TargetEntry *target = get_tle_by_resno(plan->targetlist,
    2142             :                                                keyresno);
    2143             :         char       *exprstr;
    2144             : 
    2145         281 :         if (!target)
    2146           0 :             elog(ERROR, "no tlist entry for key %d", keyresno);
    2147             :         /* Deparse the expression, showing any top-level cast */
    2148         281 :         exprstr = deparse_expression((Node *) target->expr, context,
    2149             :                                      useprefix, true);
    2150         281 :         resetStringInfo(&sortkeybuf);
    2151         281 :         appendStringInfoString(&sortkeybuf, exprstr);
    2152             :         /* Append sort order information, if relevant */
    2153         281 :         if (sortOperators != NULL)
    2154         716 :             show_sortorder_options(&sortkeybuf,
    2155         179 :                                    (Node *) target->expr,
    2156         179 :                                    sortOperators[keyno],
    2157         179 :                                    collations[keyno],
    2158         179 :                                    nullsFirst[keyno]);
    2159             :         /* Emit one property-list item per sort key */
    2160         281 :         result = lappend(result, pstrdup(sortkeybuf.data));
    2161             :     }
    2162             : 
    2163         175 :     ExplainPropertyList(qlabel, result, es);
    2164             : }
    2165             : 
    2166             : /*
    2167             :  * Append nondefault characteristics of the sort ordering of a column to buf
    2168             :  * (collation, direction, NULLS FIRST/LAST)
    2169             :  */
    2170             : static void
    2171         179 : show_sortorder_options(StringInfo buf, Node *sortexpr,
    2172             :                        Oid sortOperator, Oid collation, bool nullsFirst)
    2173             : {
    2174         179 :     Oid         sortcoltype = exprType(sortexpr);
    2175         179 :     bool        reverse = false;
    2176             :     TypeCacheEntry *typentry;
    2177             : 
    2178         179 :     typentry = lookup_type_cache(sortcoltype,
    2179             :                                  TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
    2180             : 
    2181             :     /*
    2182             :      * Print COLLATE if it's not default.  There are some cases where this is
    2183             :      * redundant, eg if expression is a column whose declared collation is
    2184             :      * that collation, but it's hard to distinguish that here.
    2185             :      */
    2186         179 :     if (OidIsValid(collation) && collation != DEFAULT_COLLATION_OID)
    2187             :     {
    2188           4 :         char       *collname = get_collation_name(collation);
    2189             : 
    2190           4 :         if (collname == NULL)
    2191           0 :             elog(ERROR, "cache lookup failed for collation %u", collation);
    2192           4 :         appendStringInfo(buf, " COLLATE %s", quote_identifier(collname));
    2193             :     }
    2194             : 
    2195             :     /* Print direction if not ASC, or USING if non-default sort operator */
    2196         179 :     if (sortOperator == typentry->gt_opr)
    2197             :     {
    2198           6 :         appendStringInfoString(buf, " DESC");
    2199           6 :         reverse = true;
    2200             :     }
    2201         173 :     else if (sortOperator != typentry->lt_opr)
    2202             :     {
    2203           2 :         char       *opname = get_opname(sortOperator);
    2204             : 
    2205           2 :         if (opname == NULL)
    2206           0 :             elog(ERROR, "cache lookup failed for operator %u", sortOperator);
    2207           2 :         appendStringInfo(buf, " USING %s", opname);
    2208             :         /* Determine whether operator would be considered ASC or DESC */
    2209           2 :         (void) get_equality_op_for_ordering_op(sortOperator, &reverse);
    2210             :     }
    2211             : 
    2212             :     /* Add NULLS FIRST/LAST only if it wouldn't be default */
    2213         179 :     if (nullsFirst && !reverse)
    2214             :     {
    2215           1 :         appendStringInfoString(buf, " NULLS FIRST");
    2216             :     }
    2217         178 :     else if (!nullsFirst && reverse)
    2218             :     {
    2219           0 :         appendStringInfoString(buf, " NULLS LAST");
    2220             :     }
    2221         179 : }
    2222             : 
    2223             : /*
    2224             :  * Show TABLESAMPLE properties
    2225             :  */
    2226             : static void
    2227           9 : show_tablesample(TableSampleClause *tsc, PlanState *planstate,
    2228             :                  List *ancestors, ExplainState *es)
    2229             : {
    2230             :     List       *context;
    2231             :     bool        useprefix;
    2232             :     char       *method_name;
    2233           9 :     List       *params = NIL;
    2234             :     char       *repeatable;
    2235             :     ListCell   *lc;
    2236             : 
    2237             :     /* Set up deparsing context */
    2238           9 :     context = set_deparse_context_planstate(es->deparse_cxt,
    2239             :                                             (Node *) planstate,
    2240             :                                             ancestors);
    2241           9 :     useprefix = list_length(es->rtable) > 1;
    2242             : 
    2243             :     /* Get the tablesample method name */
    2244           9 :     method_name = get_func_name(tsc->tsmhandler);
    2245             : 
    2246             :     /* Deparse parameter expressions */
    2247          18 :     foreach(lc, tsc->args)
    2248             :     {
    2249           9 :         Node       *arg = (Node *) lfirst(lc);
    2250             : 
    2251           9 :         params = lappend(params,
    2252           9 :                          deparse_expression(arg, context,
    2253             :                                             useprefix, false));
    2254             :     }
    2255           9 :     if (tsc->repeatable)
    2256           2 :         repeatable = deparse_expression((Node *) tsc->repeatable, context,
    2257             :                                         useprefix, false);
    2258             :     else
    2259           7 :         repeatable = NULL;
    2260             : 
    2261             :     /* Print results */
    2262           9 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    2263             :     {
    2264           9 :         bool        first = true;
    2265             : 
    2266           9 :         appendStringInfoSpaces(es->str, es->indent * 2);
    2267           9 :         appendStringInfo(es->str, "Sampling: %s (", method_name);
    2268          18 :         foreach(lc, params)
    2269             :         {
    2270           9 :             if (!first)
    2271           0 :                 appendStringInfoString(es->str, ", ");
    2272           9 :             appendStringInfoString(es->str, (const char *) lfirst(lc));
    2273           9 :             first = false;
    2274             :         }
    2275           9 :         appendStringInfoChar(es->str, ')');
    2276           9 :         if (repeatable)
    2277           2 :             appendStringInfo(es->str, " REPEATABLE (%s)", repeatable);
    2278           9 :         appendStringInfoChar(es->str, '\n');
    2279             :     }
    2280             :     else
    2281             :     {
    2282           0 :         ExplainPropertyText("Sampling Method", method_name, es);
    2283           0 :         ExplainPropertyList("Sampling Parameters", params, es);
    2284           0 :         if (repeatable)
    2285           0 :             ExplainPropertyText("Repeatable Seed", repeatable, es);
    2286             :     }
    2287           9 : }
    2288             : 
    2289             : /*
    2290             :  * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
    2291             :  */
    2292             : static void
    2293          98 : show_sort_info(SortState *sortstate, ExplainState *es)
    2294             : {
    2295          98 :     if (!es->analyze)
    2296         195 :         return;
    2297             : 
    2298           1 :     if (sortstate->sort_Done && sortstate->tuplesortstate != NULL)
    2299             :     {
    2300           1 :         Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;
    2301             :         TuplesortInstrumentation stats;
    2302             :         const char *sortMethod;
    2303             :         const char *spaceType;
    2304             :         long        spaceUsed;
    2305             : 
    2306           1 :         tuplesort_get_stats(state, &stats);
    2307           1 :         sortMethod = tuplesort_method_name(stats.sortMethod);
    2308           1 :         spaceType = tuplesort_space_type_name(stats.spaceType);
    2309           1 :         spaceUsed = stats.spaceUsed;
    2310             : 
    2311           1 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    2312             :         {
    2313           1 :             appendStringInfoSpaces(es->str, es->indent * 2);
    2314           1 :             appendStringInfo(es->str, "Sort Method: %s  %s: %ldkB\n",
    2315             :                              sortMethod, spaceType, spaceUsed);
    2316             :         }
    2317             :         else
    2318             :         {
    2319           0 :             ExplainPropertyText("Sort Method", sortMethod, es);
    2320           0 :             ExplainPropertyLong("Sort Space Used", spaceUsed, es);
    2321           0 :             ExplainPropertyText("Sort Space Type", spaceType, es);
    2322             :         }
    2323             :     }
    2324             : 
    2325           1 :     if (sortstate->shared_info != NULL)
    2326             :     {
    2327             :         int         n;
    2328           0 :         bool        opened_group = false;
    2329             : 
    2330           0 :         for (n = 0; n < sortstate->shared_info->num_workers; n++)
    2331             :         {
    2332             :             TuplesortInstrumentation *sinstrument;
    2333             :             const char *sortMethod;
    2334             :             const char *spaceType;
    2335             :             long        spaceUsed;
    2336             : 
    2337           0 :             sinstrument = &sortstate->shared_info->sinstrument[n];
    2338           0 :             if (sinstrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS)
    2339           0 :                 continue;       /* ignore any unfilled slots */
    2340           0 :             sortMethod = tuplesort_method_name(sinstrument->sortMethod);
    2341           0 :             spaceType = tuplesort_space_type_name(sinstrument->spaceType);
    2342           0 :             spaceUsed = sinstrument->spaceUsed;
    2343             : 
    2344           0 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    2345             :             {
    2346           0 :                 appendStringInfoSpaces(es->str, es->indent * 2);
    2347           0 :                 appendStringInfo(es->str,
    2348             :                                  "Worker %d:  Sort Method: %s  %s: %ldkB\n",
    2349             :                                  n, sortMethod, spaceType, spaceUsed);
    2350             :             }
    2351             :             else
    2352             :             {
    2353           0 :                 if (!opened_group)
    2354             :                 {
    2355           0 :                     ExplainOpenGroup("Workers", "Workers", false, es);
    2356           0 :                     opened_group = true;
    2357             :                 }
    2358           0 :                 ExplainOpenGroup("Worker", NULL, true, es);
    2359           0 :                 ExplainPropertyInteger("Worker Number", n, es);
    2360           0 :                 ExplainPropertyText("Sort Method", sortMethod, es);
    2361           0 :                 ExplainPropertyLong("Sort Space Used", spaceUsed, es);
    2362           0 :                 ExplainPropertyText("Sort Space Type", spaceType, es);
    2363           0 :                 ExplainCloseGroup("Worker", NULL, true, es);
    2364             :             }
    2365             :         }
    2366           0 :         if (opened_group)
    2367           0 :             ExplainCloseGroup("Workers", "Workers", false, es);
    2368             :     }
    2369             : }
    2370             : 
    2371             : /*
    2372             :  * Show information on hash buckets/batches.
    2373             :  */
    2374             : static void
    2375          53 : show_hash_info(HashState *hashstate, ExplainState *es)
    2376             : {
    2377             :     HashJoinTable hashtable;
    2378             : 
    2379          53 :     hashtable = hashstate->hashtable;
    2380             : 
    2381          53 :     if (hashtable)
    2382             :     {
    2383           0 :         long        spacePeakKb = (hashtable->spacePeak + 1023) / 1024;
    2384             : 
    2385           0 :         if (es->format != EXPLAIN_FORMAT_TEXT)
    2386             :         {
    2387           0 :             ExplainPropertyLong("Hash Buckets", hashtable->nbuckets, es);
    2388           0 :             ExplainPropertyLong("Original Hash Buckets",
    2389           0 :                                 hashtable->nbuckets_original, es);
    2390           0 :             ExplainPropertyLong("Hash Batches", hashtable->nbatch, es);
    2391           0 :             ExplainPropertyLong("Original Hash Batches",
    2392           0 :                                 hashtable->nbatch_original, es);
    2393           0 :             ExplainPropertyLong("Peak Memory Usage", spacePeakKb, es);
    2394             :         }
    2395           0 :         else if (hashtable->nbatch_original != hashtable->nbatch ||
    2396           0 :                  hashtable->nbuckets_original != hashtable->nbuckets)
    2397             :         {
    2398           0 :             appendStringInfoSpaces(es->str, es->indent * 2);
    2399           0 :             appendStringInfo(es->str,
    2400             :                              "Buckets: %d (originally %d)  Batches: %d (originally %d)  Memory Usage: %ldkB\n",
    2401             :                              hashtable->nbuckets,
    2402             :                              hashtable->nbuckets_original,
    2403             :                              hashtable->nbatch,
    2404             :                              hashtable->nbatch_original,
    2405             :                              spacePeakKb);
    2406             :         }
    2407             :         else
    2408             :         {
    2409           0 :             appendStringInfoSpaces(es->str, es->indent * 2);
    2410           0 :             appendStringInfo(es->str,
    2411             :                              "Buckets: %d  Batches: %d  Memory Usage: %ldkB\n",
    2412             :                              hashtable->nbuckets, hashtable->nbatch,
    2413             :                              spacePeakKb);
    2414             :         }
    2415             :     }
    2416          53 : }
    2417             : 
    2418             : /*
    2419             :  * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
    2420             :  */
    2421             : static void
    2422           0 : show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es)
    2423             : {
    2424           0 :     if (es->format != EXPLAIN_FORMAT_TEXT)
    2425             :     {
    2426           0 :         ExplainPropertyLong("Exact Heap Blocks", planstate->exact_pages, es);
    2427           0 :         ExplainPropertyLong("Lossy Heap Blocks", planstate->lossy_pages, es);
    2428             :     }
    2429             :     else
    2430             :     {
    2431           0 :         if (planstate->exact_pages > 0 || planstate->lossy_pages > 0)
    2432             :         {
    2433           0 :             appendStringInfoSpaces(es->str, es->indent * 2);
    2434           0 :             appendStringInfoString(es->str, "Heap Blocks:");
    2435           0 :             if (planstate->exact_pages > 0)
    2436           0 :                 appendStringInfo(es->str, " exact=%ld", planstate->exact_pages);
    2437           0 :             if (planstate->lossy_pages > 0)
    2438           0 :                 appendStringInfo(es->str, " lossy=%ld", planstate->lossy_pages);
    2439           0 :             appendStringInfoChar(es->str, '\n');
    2440             :         }
    2441             :     }
    2442           0 : }
    2443             : 
    2444             : /*
    2445             :  * If it's EXPLAIN ANALYZE, show instrumentation information for a plan node
    2446             :  *
    2447             :  * "which" identifies which instrumentation counter to print
    2448             :  */
    2449             : static void
    2450        1234 : show_instrumentation_count(const char *qlabel, int which,
    2451             :                            PlanState *planstate, ExplainState *es)
    2452             : {
    2453             :     double      nfiltered;
    2454             :     double      nloops;
    2455             : 
    2456        1234 :     if (!es->analyze || !planstate->instrument)
    2457        2466 :         return;
    2458             : 
    2459           2 :     if (which == 2)
    2460           1 :         nfiltered = planstate->instrument->nfiltered2;
    2461             :     else
    2462           1 :         nfiltered = planstate->instrument->nfiltered1;
    2463           2 :     nloops = planstate->instrument->nloops;
    2464             : 
    2465             :     /* In text mode, suppress zero counts; they're not interesting enough */
    2466           2 :     if (nfiltered > 0 || es->format != EXPLAIN_FORMAT_TEXT)
    2467             :     {
    2468           0 :         if (nloops > 0)
    2469           0 :             ExplainPropertyFloat(qlabel, nfiltered / nloops, 0, es);
    2470             :         else
    2471           0 :             ExplainPropertyFloat(qlabel, 0.0, 0, es);
    2472             :     }
    2473             : }
    2474             : 
    2475             : /*
    2476             :  * Show extra information for a ForeignScan node.
    2477             :  */
    2478             : static void
    2479           0 : show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
    2480             : {
    2481           0 :     FdwRoutine *fdwroutine = fsstate->fdwroutine;
    2482             : 
    2483             :     /* Let the FDW emit whatever fields it wants */
    2484           0 :     if (((ForeignScan *) fsstate->ss.ps.plan)->operation != CMD_SELECT)
    2485             :     {
    2486           0 :         if (fdwroutine->ExplainDirectModify != NULL)
    2487           0 :             fdwroutine->ExplainDirectModify(fsstate, es);
    2488             :     }
    2489             :     else
    2490             :     {
    2491           0 :         if (fdwroutine->ExplainForeignScan != NULL)
    2492           0 :             fdwroutine->ExplainForeignScan(fsstate, es);
    2493             :     }
    2494           0 : }
    2495             : 
    2496             : /*
    2497             :  * Fetch the name of an index in an EXPLAIN
    2498             :  *
    2499             :  * We allow plugins to get control here so that plans involving hypothetical
    2500             :  * indexes can be explained.
    2501             :  */
    2502             : static const char *
    2503         651 : explain_get_index_name(Oid indexId)
    2504             : {
    2505             :     const char *result;
    2506             : 
    2507         651 :     if (explain_get_index_name_hook)
    2508           0 :         result = (*explain_get_index_name_hook) (indexId);
    2509             :     else
    2510         651 :         result = NULL;
    2511         651 :     if (result == NULL)
    2512             :     {
    2513             :         /* default behavior: look in the catalogs and quote it */
    2514         651 :         result = get_rel_name(indexId);
    2515         651 :         if (result == NULL)
    2516           0 :             elog(ERROR, "cache lookup failed for index %u", indexId);
    2517         651 :         result = quote_identifier(result);
    2518             :     }
    2519         651 :     return result;
    2520             : }
    2521             : 
    2522             : /*
    2523             :  * Show buffer usage details.
    2524             :  */
    2525             : static void
    2526           0 : show_buffer_usage(ExplainState *es, const BufferUsage *usage)
    2527             : {
    2528           0 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    2529             :     {
    2530           0 :         bool        has_shared = (usage->shared_blks_hit > 0 ||
    2531           0 :                                   usage->shared_blks_read > 0 ||
    2532           0 :                                   usage->shared_blks_dirtied > 0 ||
    2533           0 :                                   usage->shared_blks_written > 0);
    2534           0 :         bool        has_local = (usage->local_blks_hit > 0 ||
    2535           0 :                                  usage->local_blks_read > 0 ||
    2536           0 :                                  usage->local_blks_dirtied > 0 ||
    2537           0 :                                  usage->local_blks_written > 0);
    2538           0 :         bool        has_temp = (usage->temp_blks_read > 0 ||
    2539           0 :                                 usage->temp_blks_written > 0);
    2540           0 :         bool        has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) ||
    2541           0 :                                   !INSTR_TIME_IS_ZERO(usage->blk_write_time));
    2542             : 
    2543             :         /* Show only positive counter values. */
    2544           0 :         if (has_shared || has_local || has_temp)
    2545             :         {
    2546           0 :             appendStringInfoSpaces(es->str, es->indent * 2);
    2547           0 :             appendStringInfoString(es->str, "Buffers:");
    2548             : 
    2549           0 :             if (has_shared)
    2550             :             {
    2551           0 :                 appendStringInfoString(es->str, " shared");
    2552           0 :                 if (usage->shared_blks_hit > 0)
    2553           0 :                     appendStringInfo(es->str, " hit=%ld",
    2554             :                                      usage->shared_blks_hit);
    2555           0 :                 if (usage->shared_blks_read > 0)
    2556           0 :                     appendStringInfo(es->str, " read=%ld",
    2557             :                                      usage->shared_blks_read);
    2558           0 :                 if (usage->shared_blks_dirtied > 0)
    2559           0 :                     appendStringInfo(es->str, " dirtied=%ld",
    2560             :                                      usage->shared_blks_dirtied);
    2561           0 :                 if (usage->shared_blks_written > 0)
    2562           0 :                     appendStringInfo(es->str, " written=%ld",
    2563             :                                      usage->shared_blks_written);
    2564           0 :                 if (has_local || has_temp)
    2565           0 :                     appendStringInfoChar(es->str, ',');
    2566             :             }
    2567           0 :             if (has_local)
    2568             :             {
    2569           0 :                 appendStringInfoString(es->str, " local");
    2570           0 :                 if (usage->local_blks_hit > 0)
    2571           0 :                     appendStringInfo(es->str, " hit=%ld",
    2572             :                                      usage->local_blks_hit);
    2573           0 :                 if (usage->local_blks_read > 0)
    2574           0 :                     appendStringInfo(es->str, " read=%ld",
    2575             :                                      usage->local_blks_read);
    2576           0 :                 if (usage->local_blks_dirtied > 0)
    2577           0 :                     appendStringInfo(es->str, " dirtied=%ld",
    2578             :                                      usage->local_blks_dirtied);
    2579           0 :                 if (usage->local_blks_written > 0)
    2580           0 :                     appendStringInfo(es->str, " written=%ld",
    2581             :                                      usage->local_blks_written);
    2582           0 :                 if (has_temp)
    2583           0 :                     appendStringInfoChar(es->str, ',');
    2584             :             }
    2585           0 :             if (has_temp)
    2586             :             {
    2587           0 :                 appendStringInfoString(es->str, " temp");
    2588           0 :                 if (usage->temp_blks_read > 0)
    2589           0 :                     appendStringInfo(es->str, " read=%ld",
    2590             :                                      usage->temp_blks_read);
    2591           0 :                 if (usage->temp_blks_written > 0)
    2592           0 :                     appendStringInfo(es->str, " written=%ld",
    2593             :                                      usage->temp_blks_written);
    2594             :             }
    2595           0 :             appendStringInfoChar(es->str, '\n');
    2596             :         }
    2597             : 
    2598             :         /* As above, show only positive counter values. */
    2599           0 :         if (has_timing)
    2600             :         {
    2601           0 :             appendStringInfoSpaces(es->str, es->indent * 2);
    2602           0 :             appendStringInfoString(es->str, "I/O Timings:");
    2603           0 :             if (!INSTR_TIME_IS_ZERO(usage->blk_read_time))
    2604           0 :                 appendStringInfo(es->str, " read=%0.3f",
    2605           0 :                                  INSTR_TIME_GET_MILLISEC(usage->blk_read_time));
    2606           0 :             if (!INSTR_TIME_IS_ZERO(usage->blk_write_time))
    2607           0 :                 appendStringInfo(es->str, " write=%0.3f",
    2608           0 :                                  INSTR_TIME_GET_MILLISEC(usage->blk_write_time));
    2609           0 :             appendStringInfoChar(es->str, '\n');
    2610             :         }
    2611             :     }
    2612             :     else
    2613             :     {
    2614           0 :         ExplainPropertyLong("Shared Hit Blocks", usage->shared_blks_hit, es);
    2615           0 :         ExplainPropertyLong("Shared Read Blocks", usage->shared_blks_read, es);
    2616           0 :         ExplainPropertyLong("Shared Dirtied Blocks", usage->shared_blks_dirtied, es);
    2617           0 :         ExplainPropertyLong("Shared Written Blocks", usage->shared_blks_written, es);
    2618           0 :         ExplainPropertyLong("Local Hit Blocks", usage->local_blks_hit, es);
    2619           0 :         ExplainPropertyLong("Local Read Blocks", usage->local_blks_read, es);
    2620           0 :         ExplainPropertyLong("Local Dirtied Blocks", usage->local_blks_dirtied, es);
    2621           0 :         ExplainPropertyLong("Local Written Blocks", usage->local_blks_written, es);
    2622           0 :         ExplainPropertyLong("Temp Read Blocks", usage->temp_blks_read, es);
    2623           0 :         ExplainPropertyLong("Temp Written Blocks", usage->temp_blks_written, es);
    2624           0 :         if (track_io_timing)
    2625             :         {
    2626           0 :             ExplainPropertyFloat("I/O Read Time", INSTR_TIME_GET_MILLISEC(usage->blk_read_time), 3, es);
    2627           0 :             ExplainPropertyFloat("I/O Write Time", INSTR_TIME_GET_MILLISEC(usage->blk_write_time), 3, es);
    2628             :         }
    2629             :     }
    2630           0 : }
    2631             : 
    2632             : /*
    2633             :  * Add some additional details about an IndexScan or IndexOnlyScan
    2634             :  */
    2635             : static void
    2636         324 : ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
    2637             :                         ExplainState *es)
    2638             : {
    2639         324 :     const char *indexname = explain_get_index_name(indexid);
    2640             : 
    2641         324 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    2642             :     {
    2643         324 :         if (ScanDirectionIsBackward(indexorderdir))
    2644          20 :             appendStringInfoString(es->str, " Backward");
    2645         324 :         appendStringInfo(es->str, " using %s", indexname);
    2646             :     }
    2647             :     else
    2648             :     {
    2649             :         const char *scandir;
    2650             : 
    2651           0 :         switch (indexorderdir)
    2652             :         {
    2653             :             case BackwardScanDirection:
    2654           0 :                 scandir = "Backward";
    2655           0 :                 break;
    2656             :             case NoMovementScanDirection:
    2657           0 :                 scandir = "NoMovement";
    2658           0 :                 break;
    2659             :             case ForwardScanDirection:
    2660           0 :                 scandir = "Forward";
    2661           0 :                 break;
    2662             :             default:
    2663           0 :                 scandir = "???";
    2664           0 :                 break;
    2665             :         }
    2666           0 :         ExplainPropertyText("Scan Direction", scandir, es);
    2667           0 :         ExplainPropertyText("Index Name", indexname, es);
    2668             :     }
    2669         324 : }
    2670             : 
    2671             : /*
    2672             :  * Show the target of a Scan node
    2673             :  */
    2674             : static void
    2675        1570 : ExplainScanTarget(Scan *plan, ExplainState *es)
    2676             : {
    2677        1570 :     ExplainTargetRel((Plan *) plan, plan->scanrelid, es);
    2678        1570 : }
    2679             : 
    2680             : /*
    2681             :  * Show the target of a ModifyTable node
    2682             :  *
    2683             :  * Here we show the nominal target (ie, the relation that was named in the
    2684             :  * original query).  If the actual target(s) is/are different, we'll show them
    2685             :  * in show_modifytable_info().
    2686             :  */
    2687             : static void
    2688          61 : ExplainModifyTarget(ModifyTable *plan, ExplainState *es)
    2689             : {
    2690          61 :     ExplainTargetRel((Plan *) plan, plan->nominalRelation, es);
    2691          61 : }
    2692             : 
    2693             : /*
    2694             :  * Show the target relation of a scan or modify node
    2695             :  */
    2696             : static void
    2697        1655 : ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
    2698             : {
    2699        1655 :     char       *objectname = NULL;
    2700        1655 :     char       *namespace = NULL;
    2701        1655 :     const char *objecttag = NULL;
    2702             :     RangeTblEntry *rte;
    2703             :     char       *refname;
    2704             : 
    2705        1655 :     rte = rt_fetch(rti, es->rtable);
    2706        1655 :     refname = (char *) list_nth(es->rtable_names, rti - 1);
    2707        1655 :     if (refname == NULL)
    2708           0 :         refname = rte->eref->aliasname;
    2709             : 
    2710        1655 :     switch (nodeTag(plan))
    2711             :     {
    2712             :         case T_SeqScan:
    2713             :         case T_SampleScan:
    2714             :         case T_IndexScan:
    2715             :         case T_IndexOnlyScan:
    2716             :         case T_BitmapHeapScan:
    2717             :         case T_TidScan:
    2718             :         case T_ForeignScan:
    2719             :         case T_CustomScan:
    2720             :         case T_ModifyTable:
    2721             :             /* Assert it's on a real relation */
    2722        1552 :             Assert(rte->rtekind == RTE_RELATION);
    2723        1552 :             objectname = get_rel_name(rte->relid);
    2724        1552 :             if (es->verbose)
    2725         180 :                 namespace = get_namespace_name(get_rel_namespace(rte->relid));
    2726        1552 :             objecttag = "Relation Name";
    2727        1552 :             break;
    2728             :         case T_FunctionScan:
    2729             :             {
    2730          23 :                 FunctionScan *fscan = (FunctionScan *) plan;
    2731             : 
    2732             :                 /* Assert it's on a RangeFunction */
    2733          23 :                 Assert(rte->rtekind == RTE_FUNCTION);
    2734             : 
    2735             :                 /*
    2736             :                  * If the expression is still a function call of a single
    2737             :                  * function, we can get the real name of the function.
    2738             :                  * Otherwise, punt.  (Even if it was a single function call
    2739             :                  * originally, the optimizer could have simplified it away.)
    2740             :                  */
    2741          23 :                 if (list_length(fscan->functions) == 1)
    2742             :                 {
    2743          23 :                     RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions);
    2744             : 
    2745          23 :                     if (IsA(rtfunc->funcexpr, FuncExpr))
    2746             :                     {
    2747          18 :                         FuncExpr   *funcexpr = (FuncExpr *) rtfunc->funcexpr;
    2748          18 :                         Oid         funcid = funcexpr->funcid;
    2749             : 
    2750          18 :                         objectname = get_func_name(funcid);
    2751          18 :                         if (es->verbose)
    2752           6 :                             namespace =
    2753           6 :                                 get_namespace_name(get_func_namespace(funcid));
    2754             :                     }
    2755             :                 }
    2756          23 :                 objecttag = "Function Name";
    2757             :             }
    2758          23 :             break;
    2759             :         case T_TableFuncScan:
    2760           5 :             Assert(rte->rtekind == RTE_TABLEFUNC);
    2761           5 :             objectname = "xmltable";
    2762           5 :             objecttag = "Table Function Name";
    2763           5 :             break;
    2764             :         case T_ValuesScan:
    2765          24 :             Assert(rte->rtekind == RTE_VALUES);
    2766          24 :             break;
    2767             :         case T_CteScan:
    2768             :             /* Assert it's on a non-self-reference CTE */
    2769          15 :             Assert(rte->rtekind == RTE_CTE);
    2770          15 :             Assert(!rte->self_reference);
    2771          15 :             objectname = rte->ctename;
    2772          15 :             objecttag = "CTE Name";
    2773          15 :             break;
    2774             :         case T_NamedTuplestoreScan:
    2775           0 :             Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);
    2776           0 :             objectname = rte->enrname;
    2777           0 :             objecttag = "Tuplestore Name";
    2778           0 :             break;
    2779             :         case T_WorkTableScan:
    2780             :             /* Assert it's on a self-reference CTE */
    2781           0 :             Assert(rte->rtekind == RTE_CTE);
    2782           0 :             Assert(rte->self_reference);
    2783           0 :             objectname = rte->ctename;
    2784           0 :             objecttag = "CTE Name";
    2785           0 :             break;
    2786             :         default:
    2787          36 :             break;
    2788             :     }
    2789             : 
    2790        1655 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    2791             :     {
    2792        1654 :         appendStringInfoString(es->str, " on");
    2793        1654 :         if (namespace != NULL)
    2794         186 :             appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
    2795             :                              quote_identifier(objectname));
    2796        1468 :         else if (objectname != NULL)
    2797        1403 :             appendStringInfo(es->str, " %s", quote_identifier(objectname));
    2798        1654 :         if (objectname == NULL || strcmp(refname, objectname) != 0)
    2799         386 :             appendStringInfo(es->str, " %s", quote_identifier(refname));
    2800             :     }
    2801             :     else
    2802             :     {
    2803           1 :         if (objecttag != NULL && objectname != NULL)
    2804           1 :             ExplainPropertyText(objecttag, objectname, es);
    2805           1 :         if (namespace != NULL)
    2806           0 :             ExplainPropertyText("Schema", namespace, es);
    2807           1 :         ExplainPropertyText("Alias", refname, es);
    2808             :     }
    2809        1655 : }
    2810             : 
    2811             : /*
    2812             :  * Show extra information for a ModifyTable node
    2813             :  *
    2814             :  * We have three objectives here.  First, if there's more than one target
    2815             :  * table or it's different from the nominal target, identify the actual
    2816             :  * target(s).  Second, give FDWs a chance to display extra info about foreign
    2817             :  * targets.  Third, show information about ON CONFLICT.
    2818             :  */
    2819             : static void
    2820          61 : show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
    2821             :                       ExplainState *es)
    2822             : {
    2823          61 :     ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
    2824             :     const char *operation;
    2825             :     const char *foperation;
    2826             :     bool        labeltargets;
    2827             :     int         j;
    2828          61 :     List       *idxNames = NIL;
    2829             :     ListCell   *lst;
    2830             : 
    2831          61 :     switch (node->operation)
    2832             :     {
    2833             :         case CMD_INSERT:
    2834          24 :             operation = "Insert";
    2835          24 :             foperation = "Foreign Insert";
    2836          24 :             break;
    2837             :         case CMD_UPDATE:
    2838          25 :             operation = "Update";
    2839          25 :             foperation = "Foreign Update";
    2840          25 :             break;
    2841             :         case CMD_DELETE:
    2842          12 :             operation = "Delete";
    2843          12 :             foperation = "Foreign Delete";
    2844          12 :             break;
    2845             :         default:
    2846           0 :             operation = "???";
    2847           0 :             foperation = "Foreign ???";
    2848           0 :             break;
    2849             :     }
    2850             : 
    2851             :     /* Should we explicitly label target relations? */
    2852         122 :     labeltargets = (mtstate->mt_nplans > 1 ||
    2853         108 :                     (mtstate->mt_nplans == 1 &&
    2854          54 :                      mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
    2855             : 
    2856          61 :     if (labeltargets)
    2857           7 :         ExplainOpenGroup("Target Tables", "Target Tables", false, es);
    2858             : 
    2859         139 :     for (j = 0; j < mtstate->mt_nplans; j++)
    2860             :     {
    2861          78 :         ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
    2862          78 :         FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
    2863             : 
    2864          78 :         if (labeltargets)
    2865             :         {
    2866             :             /* Open a group for this target */
    2867          24 :             ExplainOpenGroup("Target Table", NULL, true, es);
    2868             : 
    2869             :             /*
    2870             :              * In text mode, decorate each target with operation type, so that
    2871             :              * ExplainTargetRel's output of " on foo" will read nicely.
    2872             :              */
    2873          24 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    2874             :             {
    2875          24 :                 appendStringInfoSpaces(es->str, es->indent * 2);
    2876          24 :                 appendStringInfoString(es->str,
    2877             :                                        fdwroutine ? foperation : operation);
    2878             :             }
    2879             : 
    2880             :             /* Identify target */
    2881          24 :             ExplainTargetRel((Plan *) node,
    2882             :                              resultRelInfo->ri_RangeTableIndex,
    2883             :                              es);
    2884             : 
    2885          24 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    2886             :             {
    2887          24 :                 appendStringInfoChar(es->str, '\n');
    2888          24 :                 es->indent++;
    2889             :             }
    2890             :         }
    2891             : 
    2892             :         /* Give FDW a chance if needed */
    2893          78 :         if (!resultRelInfo->ri_usesFdwDirectModify &&
    2894           0 :             fdwroutine != NULL &&
    2895           0 :             fdwroutine->ExplainForeignModify != NULL)
    2896             :         {
    2897           0 :             List       *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
    2898             : 
    2899           0 :             fdwroutine->ExplainForeignModify(mtstate,
    2900             :                                              resultRelInfo,
    2901             :                                              fdw_private,
    2902             :                                              j,
    2903             :                                              es);
    2904             :         }
    2905             : 
    2906          78 :         if (labeltargets)
    2907             :         {
    2908             :             /* Undo the indentation we added in text format */
    2909          24 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    2910          24 :                 es->indent--;
    2911             : 
    2912             :             /* Close the group */
    2913          24 :             ExplainCloseGroup("Target Table", NULL, true, es);
    2914             :         }
    2915             :     }
    2916             : 
    2917             :     /* Gather names of ON CONFLICT arbiter indexes */
    2918          90 :     foreach(lst, node->arbiterIndexes)
    2919             :     {
    2920          29 :         char       *indexname = get_rel_name(lfirst_oid(lst));
    2921             : 
    2922          29 :         idxNames = lappend(idxNames, indexname);
    2923             :     }
    2924             : 
    2925          61 :     if (node->onConflictAction != ONCONFLICT_NONE)
    2926             :     {
    2927          20 :         ExplainProperty("Conflict Resolution",
    2928          20 :                         node->onConflictAction == ONCONFLICT_NOTHING ?
    2929             :                         "NOTHING" : "UPDATE",
    2930             :                         false, es);
    2931             : 
    2932             :         /*
    2933             :          * Don't display arbiter indexes at all when DO NOTHING variant
    2934             :          * implicitly ignores all conflicts
    2935             :          */
    2936          20 :         if (idxNames)
    2937          20 :             ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es);
    2938             : 
    2939             :         /* ON CONFLICT DO UPDATE WHERE qual is specially displayed */
    2940          20 :         if (node->onConflictWhere)
    2941             :         {
    2942           7 :             show_upper_qual((List *) node->onConflictWhere, "Conflict Filter",
    2943             :                             &mtstate->ps, ancestors, es);
    2944           7 :             show_instrumentation_count("Rows Removed by Conflict Filter", 1, &mtstate->ps, es);
    2945             :         }
    2946             : 
    2947             :         /* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */
    2948          20 :         if (es->analyze && mtstate->ps.instrument)
    2949             :         {
    2950             :             double      total;
    2951             :             double      insert_path;
    2952             :             double      other_path;
    2953             : 
    2954           0 :             InstrEndLoop(mtstate->mt_plans[0]->instrument);
    2955             : 
    2956             :             /* count the number of source rows */
    2957           0 :             total = mtstate->mt_plans[0]->instrument->ntuples;
    2958           0 :             other_path = mtstate->ps.instrument->nfiltered2;
    2959           0 :             insert_path = total - other_path;
    2960             : 
    2961           0 :             ExplainPropertyFloat("Tuples Inserted", insert_path, 0, es);
    2962           0 :             ExplainPropertyFloat("Conflicting Tuples", other_path, 0, es);
    2963             :         }
    2964             :     }
    2965             : 
    2966          61 :     if (labeltargets)
    2967           7 :         ExplainCloseGroup("Target Tables", "Target Tables", false, es);
    2968          61 : }
    2969             : 
    2970             : /*
    2971             :  * Explain the constituent plans of a ModifyTable, Append, MergeAppend,
    2972             :  * BitmapAnd, or BitmapOr node.
    2973             :  *
    2974             :  * The ancestors list should already contain the immediate parent of these
    2975             :  * plans.
    2976             :  *
    2977             :  * Note: we don't actually need to examine the Plan list members, but
    2978             :  * we need the list in order to determine the length of the PlanState array.
    2979             :  */
    2980             : static void
    2981         174 : ExplainMemberNodes(List *plans, PlanState **planstates,
    2982             :                    List *ancestors, ExplainState *es)
    2983             : {
    2984         174 :     int         nplans = list_length(plans);
    2985             :     int         j;
    2986             : 
    2987         559 :     for (j = 0; j < nplans; j++)
    2988         385 :         ExplainNode(planstates[j], ancestors,
    2989             :                     "Member", NULL, es);
    2990         174 : }
    2991             : 
    2992             : /*
    2993             :  * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).
    2994             :  *
    2995             :  * The ancestors list should already contain the immediate parent of these
    2996             :  * SubPlanStates.
    2997             :  */
    2998             : static void
    2999          78 : ExplainSubPlans(List *plans, List *ancestors,
    3000             :                 const char *relationship, ExplainState *es)
    3001             : {
    3002             :     ListCell   *lst;
    3003             : 
    3004         173 :     foreach(lst, plans)
    3005             :     {
    3006          95 :         SubPlanState *sps = (SubPlanState *) lfirst(lst);
    3007          95 :         SubPlan    *sp = sps->subplan;
    3008             : 
    3009             :         /*
    3010             :          * There can be multiple SubPlan nodes referencing the same physical
    3011             :          * subplan (same plan_id, which is its index in PlannedStmt.subplans).
    3012             :          * We should print a subplan only once, so track which ones we already
    3013             :          * printed.  This state must be global across the plan tree, since the
    3014             :          * duplicate nodes could be in different plan nodes, eg both a bitmap
    3015             :          * indexscan's indexqual and its parent heapscan's recheck qual.  (We
    3016             :          * do not worry too much about which plan node we show the subplan as
    3017             :          * attached to in such cases.)
    3018             :          */
    3019          95 :         if (bms_is_member(sp->plan_id, es->printed_subplans))
    3020          12 :             continue;
    3021          83 :         es->printed_subplans = bms_add_member(es->printed_subplans,
    3022             :                                               sp->plan_id);
    3023             : 
    3024          83 :         ExplainNode(sps->planstate, ancestors,
    3025          83 :                     relationship, sp->plan_name, es);
    3026             :     }
    3027          78 : }
    3028             : 
    3029             : /*
    3030             :  * Explain a list of children of a CustomScan.
    3031             :  */
    3032             : static void
    3033           0 : ExplainCustomChildren(CustomScanState *css, List *ancestors, ExplainState *es)
    3034             : {
    3035             :     ListCell   *cell;
    3036           0 :     const char *label =
    3037           0 :     (list_length(css->custom_ps) != 1 ? "children" : "child");
    3038             : 
    3039           0 :     foreach(cell, css->custom_ps)
    3040           0 :         ExplainNode((PlanState *) lfirst(cell), ancestors, label, NULL, es);
    3041           0 : }
    3042             : 
    3043             : /*
    3044             :  * Explain a property, such as sort keys or targets, that takes the form of
    3045             :  * a list of unlabeled items.  "data" is a list of C strings.
    3046             :  */
    3047             : void
    3048         648 : ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
    3049             : {
    3050             :     ListCell   *lc;
    3051         648 :     bool        first = true;
    3052             : 
    3053         648 :     switch (es->format)
    3054             :     {
    3055             :         case EXPLAIN_FORMAT_TEXT:
    3056         647 :             appendStringInfoSpaces(es->str, es->indent * 2);
    3057         647 :             appendStringInfo(es->str, "%s: ", qlabel);
    3058        1884 :             foreach(lc, data)
    3059             :             {
    3060        1237 :                 if (!first)
    3061         590 :                     appendStringInfoString(es->str, ", ");
    3062        1237 :                 appendStringInfoString(es->str, (const char *) lfirst(lc));
    3063        1237 :                 first = false;
    3064             :             }
    3065         647 :             appendStringInfoChar(es->str, '\n');
    3066         647 :             break;
    3067             : 
    3068             :         case EXPLAIN_FORMAT_XML:
    3069           0 :             ExplainXMLTag(qlabel, X_OPENING, es);
    3070           0 :             foreach(lc, data)
    3071             :             {
    3072             :                 char       *str;
    3073             : 
    3074           0 :                 appendStringInfoSpaces(es->str, es->indent * 2 + 2);
    3075           0 :                 appendStringInfoString(es->str, "<Item>");
    3076           0 :                 str = escape_xml((const char *) lfirst(lc));
    3077           0 :                 appendStringInfoString(es->str, str);
    3078           0 :                 pfree(str);
    3079           0 :                 appendStringInfoString(es->str, "</Item>\n");
    3080             :             }
    3081           0 :             ExplainXMLTag(qlabel, X_CLOSING, es);
    3082           0 :             break;
    3083             : 
    3084             :         case EXPLAIN_FORMAT_JSON:
    3085           1 :             ExplainJSONLineEnding(es);
    3086           1 :             appendStringInfoSpaces(es->str, es->indent * 2);
    3087           1 :             escape_json(es->str, qlabel);
    3088           1 :             appendStringInfoString(es->str, ": [");
    3089           2 :             foreach(lc, data)
    3090             :             {
    3091           1 :                 if (!first)
    3092           0 :                     appendStringInfoString(es->str, ", ");
    3093           1 :                 escape_json(es->str, (const char *) lfirst(lc));
    3094           1 :                 first = false;
    3095             :             }
    3096           1 :             appendStringInfoChar(es->str, ']');
    3097           1 :             break;
    3098             : 
    3099             :         case EXPLAIN_FORMAT_YAML:
    3100           0 :             ExplainYAMLLineStarting(es);
    3101           0 :             appendStringInfo(es->str, "%s: ", qlabel);
    3102           0 :             foreach(lc, data)
    3103             :             {
    3104           0 :                 appendStringInfoChar(es->str, '\n');
    3105           0 :                 appendStringInfoSpaces(es->str, es->indent * 2 + 2);
    3106           0 :                 appendStringInfoString(es->str, "- ");
    3107           0 :                 escape_yaml(es->str, (const char *) lfirst(lc));
    3108             :             }
    3109           0 :             break;
    3110             :     }
    3111         648 : }
    3112             : 
    3113             : /*
    3114             :  * Explain a property that takes the form of a list of unlabeled items within
    3115             :  * another list.  "data" is a list of C strings.
    3116             :  */
    3117             : void
    3118          48 : ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
    3119             : {
    3120             :     ListCell   *lc;
    3121          48 :     bool        first = true;
    3122             : 
    3123          48 :     switch (es->format)
    3124             :     {
    3125             :         case EXPLAIN_FORMAT_TEXT:
    3126             :         case EXPLAIN_FORMAT_XML:
    3127          48 :             ExplainPropertyList(qlabel, data, es);
    3128          96 :             return;
    3129             : 
    3130             :         case EXPLAIN_FORMAT_JSON:
    3131           0 :             ExplainJSONLineEnding(es);
    3132           0 :             appendStringInfoSpaces(es->str, es->indent * 2);
    3133           0 :             appendStringInfoChar(es->str, '[');
    3134           0 :             foreach(lc, data)
    3135             :             {
    3136           0 :                 if (!first)
    3137           0 :                     appendStringInfoString(es->str, ", ");
    3138           0 :                 escape_json(es->str, (const char *) lfirst(lc));
    3139           0 :                 first = false;
    3140             :             }
    3141           0 :             appendStringInfoChar(es->str, ']');
    3142           0 :             break;
    3143             : 
    3144             :         case EXPLAIN_FORMAT_YAML:
    3145           0 :             ExplainYAMLLineStarting(es);
    3146           0 :             appendStringInfoString(es->str, "- [");
    3147           0 :             foreach(lc, data)
    3148             :             {
    3149           0 :                 if (!first)
    3150           0 :                     appendStringInfoString(es->str, ", ");
    3151           0 :                 escape_yaml(es->str, (const char *) lfirst(lc));
    3152           0 :                 first = false;
    3153             :             }
    3154           0 :             appendStringInfoChar(es->str, ']');
    3155           0 :             break;
    3156             :     }
    3157             : }
    3158             : 
    3159             : /*
    3160             :  * Explain a simple property.
    3161             :  *
    3162             :  * If "numeric" is true, the value is a number (or other value that
    3163             :  * doesn't need quoting in JSON).
    3164             :  *
    3165             :  * This usually should not be invoked directly, but via one of the datatype
    3166             :  * specific routines ExplainPropertyText, ExplainPropertyInteger, etc.
    3167             :  */
    3168             : static void
    3169        1738 : ExplainProperty(const char *qlabel, const char *value, bool numeric,
    3170             :                 ExplainState *es)
    3171             : {
    3172        1738 :     switch (es->format)
    3173             :     {
    3174             :         case EXPLAIN_FORMAT_TEXT:
    3175        1728 :             appendStringInfoSpaces(es->str, es->indent * 2);
    3176        1728 :             appendStringInfo(es->str, "%s: %s\n", qlabel, value);
    3177        1728 :             break;
    3178             : 
    3179             :         case EXPLAIN_FORMAT_XML:
    3180             :             {
    3181             :                 char       *str;
    3182             : 
    3183           0 :                 appendStringInfoSpaces(es->str, es->indent * 2);
    3184           0 :                 ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es);
    3185           0 :                 str = escape_xml(value);
    3186           0 :                 appendStringInfoString(es->str, str);
    3187           0 :                 pfree(str);
    3188           0 :                 ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);
    3189           0 :                 appendStringInfoChar(es->str, '\n');
    3190             :             }
    3191           0 :             break;
    3192             : 
    3193             :         case EXPLAIN_FORMAT_JSON:
    3194          10 :             ExplainJSONLineEnding(es);
    3195          10 :             appendStringInfoSpaces(es->str, es->indent * 2);
    3196          10 :             escape_json(es->str, qlabel);
    3197          10 :             appendStringInfoString(es->str, ": ");
    3198          10 :             if (numeric)
    3199           2 :                 appendStringInfoString(es->str, value);
    3200             :             else
    3201           8 :                 escape_json(es->str, value);
    3202          10 :             break;
    3203             : 
    3204             :         case EXPLAIN_FORMAT_YAML:
    3205           0 :             ExplainYAMLLineStarting(es);
    3206           0 :             appendStringInfo(es->str, "%s: ", qlabel);
    3207           0 :             if (numeric)
    3208           0 :                 appendStringInfoString(es->str, value);
    3209             :             else
    3210           0 :                 escape_yaml(es->str, value);
    3211           0 :             break;
    3212             :     }
    3213        1738 : }
    3214             : 
    3215             : /*
    3216             :  * Explain a string-valued property.
    3217             :  */
    3218             : void
    3219        1687 : ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
    3220             : {
    3221        1687 :     ExplainProperty(qlabel, value, false, es);
    3222        1687 : }
    3223             : 
    3224             : /*
    3225             :  * Explain an integer-valued property.
    3226             :  */
    3227             : void
    3228          19 : ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es)
    3229             : {
    3230             :     char        buf[32];
    3231             : 
    3232          19 :     snprintf(buf, sizeof(buf), "%d", value);
    3233          19 :     ExplainProperty(qlabel, buf, true, es);
    3234          19 : }
    3235             : 
    3236             : /*
    3237             :  * Explain a long-integer-valued property.
    3238             :  */
    3239             : void
    3240           0 : ExplainPropertyLong(const char *qlabel, long value, ExplainState *es)
    3241             : {
    3242             :     char        buf[32];
    3243             : 
    3244           0 :     snprintf(buf, sizeof(buf), "%ld", value);
    3245           0 :     ExplainProperty(qlabel, buf, true, es);
    3246           0 : }
    3247             : 
    3248             : /*
    3249             :  * Explain a float-valued property, using the specified number of
    3250             :  * fractional digits.
    3251             :  */
    3252             : void
    3253           0 : ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
    3254             :                      ExplainState *es)
    3255             : {
    3256             :     char        buf[256];
    3257             : 
    3258           0 :     snprintf(buf, sizeof(buf), "%.*f", ndigits, value);
    3259           0 :     ExplainProperty(qlabel, buf, true, es);
    3260           0 : }
    3261             : 
    3262             : /*
    3263             :  * Explain a bool-valued property.
    3264             :  */
    3265             : void
    3266          12 : ExplainPropertyBool(const char *qlabel, bool value, ExplainState *es)
    3267             : {
    3268          12 :     ExplainProperty(qlabel, value ? "true" : "false", true, es);
    3269          12 : }
    3270             : 
    3271             : /*
    3272             :  * Open a group of related objects.
    3273             :  *
    3274             :  * objtype is the type of the group object, labelname is its label within
    3275             :  * a containing object (if any).
    3276             :  *
    3277             :  * If labeled is true, the group members will be labeled properties,
    3278             :  * while if it's false, they'll be unlabeled objects.
    3279             :  */
    3280             : static void
    3281        6447 : ExplainOpenGroup(const char *objtype, const char *labelname,
    3282             :                  bool labeled, ExplainState *es)
    3283             : {
    3284        6447 :     switch (es->format)
    3285             :     {
    3286             :         case EXPLAIN_FORMAT_TEXT:
    3287             :             /* nothing to do */
    3288        6443 :             break;
    3289             : 
    3290             :         case EXPLAIN_FORMAT_XML:
    3291           0 :             ExplainXMLTag(objtype, X_OPENING, es);
    3292           0 :             es->indent++;
    3293           0 :             break;
    3294             : 
    3295             :         case EXPLAIN_FORMAT_JSON:
    3296           4 :             ExplainJSONLineEnding(es);
    3297           4 :             appendStringInfoSpaces(es->str, 2 * es->indent);
    3298           4 :             if (labelname)
    3299             :             {
    3300           2 :                 escape_json(es->str, labelname);
    3301           2 :                 appendStringInfoString(es->str, ": ");
    3302             :             }
    3303           4 :             appendStringInfoChar(es->str, labeled ? '{' : '[');
    3304             : 
    3305             :             /*
    3306             :              * In JSON format, the grouping_stack is an integer list.  0 means
    3307             :              * we've emitted nothing at this grouping level, 1 means we've
    3308             :              * emitted something (and so the next item needs a comma). See
    3309             :              * ExplainJSONLineEnding().
    3310             :              */
    3311           4 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
    3312           4 :             es->indent++;
    3313           4 :             break;
    3314             : 
    3315             :         case EXPLAIN_FORMAT_YAML:
    3316             : 
    3317             :             /*
    3318             :              * In YAML format, the grouping stack is an integer list.  0 means
    3319             :              * we've emitted nothing at this grouping level AND this grouping
    3320             :              * level is unlabelled and must be marked with "- ".  See
    3321             :              * ExplainYAMLLineStarting().
    3322             :              */
    3323           0 :             ExplainYAMLLineStarting(es);
    3324           0 :             if (labelname)
    3325             :             {
    3326           0 :                 appendStringInfo(es->str, "%s: ", labelname);
    3327           0 :                 es->grouping_stack = lcons_int(1, es->grouping_stack);
    3328             :             }
    3329             :             else
    3330             :             {
    3331           0 :                 appendStringInfoString(es->str, "- ");
    3332           0 :                 es->grouping_stack = lcons_int(0, es->grouping_stack);
    3333             :             }
    3334           0 :             es->indent++;
    3335           0 :             break;
    3336             :     }
    3337        6447 : }
    3338             : 
    3339             : /*
    3340             :  * Close a group of related objects.
    3341             :  * Parameters must match the corresponding ExplainOpenGroup call.
    3342             :  */
    3343             : static void
    3344        6447 : ExplainCloseGroup(const char *objtype, const char *labelname,
    3345             :                   bool labeled, ExplainState *es)
    3346             : {
    3347        6447 :     switch (es->format)
    3348             :     {
    3349             :         case EXPLAIN_FORMAT_TEXT:
    3350             :             /* nothing to do */
    3351        6443 :             break;
    3352             : 
    3353             :         case EXPLAIN_FORMAT_XML:
    3354           0 :             es->indent--;
    3355           0 :             ExplainXMLTag(objtype, X_CLOSING, es);
    3356           0 :             break;
    3357             : 
    3358             :         case EXPLAIN_FORMAT_JSON:
    3359           4 :             es->indent--;
    3360           4 :             appendStringInfoChar(es->str, '\n');
    3361           4 :             appendStringInfoSpaces(es->str, 2 * es->indent);
    3362           4 :             appendStringInfoChar(es->str, labeled ? '}' : ']');
    3363           4 :             es->grouping_stack = list_delete_first(es->grouping_stack);
    3364           4 :             break;
    3365             : 
    3366             :         case EXPLAIN_FORMAT_YAML:
    3367           0 :             es->indent--;
    3368           0 :             es->grouping_stack = list_delete_first(es->grouping_stack);
    3369           0 :             break;
    3370             :     }
    3371        6447 : }
    3372             : 
    3373             : /*
    3374             :  * Emit a "dummy" group that never has any members.
    3375             :  *
    3376             :  * objtype is the type of the group object, labelname is its label within
    3377             :  * a containing object (if any).
    3378             :  */
    3379             : static void
    3380           0 : ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
    3381             : {
    3382           0 :     switch (es->format)
    3383             :     {
    3384             :         case EXPLAIN_FORMAT_TEXT:
    3385             :             /* nothing to do */
    3386           0 :             break;
    3387             : 
    3388             :         case EXPLAIN_FORMAT_XML:
    3389           0 :             ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es);
    3390           0 :             break;
    3391             : 
    3392             :         case EXPLAIN_FORMAT_JSON:
    3393           0 :             ExplainJSONLineEnding(es);
    3394           0 :             appendStringInfoSpaces(es->str, 2 * es->indent);
    3395           0 :             if (labelname)
    3396             :             {
    3397           0 :                 escape_json(es->str, labelname);
    3398           0 :                 appendStringInfoString(es->str, ": ");
    3399             :             }
    3400           0 :             escape_json(es->str, objtype);
    3401           0 :             break;
    3402             : 
    3403             :         case EXPLAIN_FORMAT_YAML:
    3404           0 :             ExplainYAMLLineStarting(es);
    3405           0 :             if (labelname)
    3406             :             {
    3407           0 :                 escape_yaml(es->str, labelname);
    3408           0 :                 appendStringInfoString(es->str, ": ");
    3409             :             }
    3410             :             else
    3411             :             {
    3412           0 :                 appendStringInfoString(es->str, "- ");
    3413             :             }
    3414           0 :             escape_yaml(es->str, objtype);
    3415           0 :             break;
    3416             :     }
    3417           0 : }
    3418             : 
    3419             : /*
    3420             :  * Emit the start-of-output boilerplate.
    3421             :  *
    3422             :  * This is just enough different from processing a subgroup that we need
    3423             :  * a separate pair of subroutines.
    3424             :  */
    3425             : void
    3426        1123 : ExplainBeginOutput(ExplainState *es)
    3427             : {
    3428        1123 :     switch (es->format)
    3429             :     {
    3430             :         case EXPLAIN_FORMAT_TEXT:
    3431             :             /* nothing to do */
    3432        1122 :             break;
    3433             : 
    3434             :         case EXPLAIN_FORMAT_XML:
    3435           0 :             appendStringInfoString(es->str,
    3436             :                                    "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
    3437           0 :             es->indent++;
    3438           0 :             break;
    3439             : 
    3440             :         case EXPLAIN_FORMAT_JSON:
    3441             :             /* top-level structure is an array of plans */
    3442           1 :             appendStringInfoChar(es->str, '[');
    3443           1 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
    3444           1 :             es->indent++;
    3445           1 :             break;
    3446             : 
    3447             :         case EXPLAIN_FORMAT_YAML:
    3448           0 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
    3449           0 :             break;
    3450             :     }
    3451        1123 : }
    3452             : 
    3453             : /*
    3454             :  * Emit the end-of-output boilerplate.
    3455             :  */
    3456             : void
    3457        1116 : ExplainEndOutput(ExplainState *es)
    3458             : {
    3459        1116 :     switch (es->format)
    3460             :     {
    3461             :         case EXPLAIN_FORMAT_TEXT:
    3462             :             /* nothing to do */
    3463        1115 :             break;
    3464             : 
    3465             :         case EXPLAIN_FORMAT_XML:
    3466           0 :             es->indent--;
    3467           0 :             appendStringInfoString(es->str, "</explain>");
    3468           0 :             break;
    3469             : 
    3470             :         case EXPLAIN_FORMAT_JSON:
    3471           1 :             es->indent--;
    3472           1 :             appendStringInfoString(es->str, "\n]");
    3473           1 :             es->grouping_stack = list_delete_first(es->grouping_stack);
    3474           1 :             break;
    3475             : 
    3476             :         case EXPLAIN_FORMAT_YAML:
    3477           0 :             es->grouping_stack = list_delete_first(es->grouping_stack);
    3478           0 :             break;
    3479             :     }
    3480        1116 : }
    3481             : 
    3482             : /*
    3483             :  * Put an appropriate separator between multiple plans
    3484             :  */
    3485             : void
    3486           2 : ExplainSeparatePlans(ExplainState *es)
    3487             : {
    3488           2 :     switch (es->format)
    3489             :     {
    3490             :         case EXPLAIN_FORMAT_TEXT:
    3491             :             /* add a blank line */
    3492           2 :             appendStringInfoChar(es->str, '\n');
    3493           2 :             break;
    3494             : 
    3495             :         case EXPLAIN_FORMAT_XML:
    3496             :         case EXPLAIN_FORMAT_JSON:
    3497             :         case EXPLAIN_FORMAT_YAML:
    3498             :             /* nothing to do */
    3499           0 :             break;
    3500             :     }
    3501           2 : }
    3502             : 
    3503             : /*
    3504             :  * Emit opening or closing XML tag.
    3505             :  *
    3506             :  * "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE.
    3507             :  * Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally
    3508             :  * add.
    3509             :  *
    3510             :  * XML restricts tag names more than our other output formats, eg they can't
    3511             :  * contain white space or slashes.  Replace invalid characters with dashes,
    3512             :  * so that for example "I/O Read Time" becomes "I-O-Read-Time".
    3513             :  */
    3514             : static void
    3515           0 : ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
    3516             : {
    3517             :     const char *s;
    3518           0 :     const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";
    3519             : 
    3520           0 :     if ((flags & X_NOWHITESPACE) == 0)
    3521           0 :         appendStringInfoSpaces(es->str, 2 * es->indent);
    3522           0 :     appendStringInfoCharMacro(es->str, '<');
    3523           0 :     if ((flags & X_CLOSING) != 0)
    3524           0 :         appendStringInfoCharMacro(es->str, '/');
    3525           0 :     for (s = tagname; *s; s++)
    3526           0 :         appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-');
    3527           0 :     if ((flags & X_CLOSE_IMMEDIATE) != 0)
    3528           0 :         appendStringInfoString(es->str, " /");
    3529           0 :     appendStringInfoCharMacro(es->str, '>');
    3530           0 :     if ((flags & X_NOWHITESPACE) == 0)
    3531           0 :         appendStringInfoCharMacro(es->str, '\n');
    3532           0 : }
    3533             : 
    3534             : /*
    3535             :  * Emit a JSON line ending.
    3536             :  *
    3537             :  * JSON requires a comma after each property but the last.  To facilitate this,
    3538             :  * in JSON format, the text emitted for each property begins just prior to the
    3539             :  * preceding line-break (and comma, if applicable).
    3540             :  */
    3541             : static void
    3542          15 : ExplainJSONLineEnding(ExplainState *es)
    3543             : {
    3544          15 :     Assert(es->format == EXPLAIN_FORMAT_JSON);
    3545          15 :     if (linitial_int(es->grouping_stack) != 0)
    3546          10 :         appendStringInfoChar(es->str, ',');
    3547             :     else
    3548           5 :         linitial_int(es->grouping_stack) = 1;
    3549          15 :     appendStringInfoChar(es->str, '\n');
    3550          15 : }
    3551             : 
    3552             : /*
    3553             :  * Indent a YAML line.
    3554             :  *
    3555             :  * YAML lines are ordinarily indented by two spaces per indentation level.
    3556             :  * The text emitted for each property begins just prior to the preceding
    3557             :  * line-break, except for the first property in an unlabelled group, for which
    3558             :  * it begins immediately after the "- " that introduces the group.  The first
    3559             :  * property of the group appears on the same line as the opening "- ".
    3560             :  */
    3561             : static void
    3562           0 : ExplainYAMLLineStarting(ExplainState *es)
    3563             : {
    3564           0 :     Assert(es->format == EXPLAIN_FORMAT_YAML);
    3565           0 :     if (linitial_int(es->grouping_stack) == 0)
    3566             :     {
    3567           0 :         linitial_int(es->grouping_stack) = 1;
    3568             :     }
    3569             :     else
    3570             :     {
    3571           0 :         appendStringInfoChar(es->str, '\n');
    3572           0 :         appendStringInfoSpaces(es->str, es->indent * 2);
    3573             :     }
    3574           0 : }
    3575             : 
    3576             : /*
    3577             :  * YAML is a superset of JSON; unfortunately, the YAML quoting rules are
    3578             :  * ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of
    3579             :  * http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything.
    3580             :  * Empty strings, strings with leading or trailing whitespace, and strings
    3581             :  * containing a variety of special characters must certainly be quoted or the
    3582             :  * output is invalid; and other seemingly harmless strings like "0xa" or
    3583             :  * "true" must be quoted, lest they be interpreted as a hexadecimal or Boolean
    3584             :  * constant rather than a string.
    3585             :  */
    3586             : static void
    3587           0 : escape_yaml(StringInfo buf, const char *str)
    3588             : {
    3589           0 :     escape_json(buf, str);
    3590           0 : }

Generated by: LCOV version 1.11