LCOV - code coverage report
Current view: top level - src/backend/commands - explain.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13beta1 Lines: 1706 2103 81.1 %
Date: 2020-06-01 09:07:10 Functions: 73 78 93.6 %
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-2020, 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_type.h"
      18             : #include "commands/createas.h"
      19             : #include "commands/defrem.h"
      20             : #include "commands/prepare.h"
      21             : #include "executor/nodeHash.h"
      22             : #include "foreign/fdwapi.h"
      23             : #include "jit/jit.h"
      24             : #include "nodes/extensible.h"
      25             : #include "nodes/makefuncs.h"
      26             : #include "nodes/nodeFuncs.h"
      27             : #include "parser/parsetree.h"
      28             : #include "rewrite/rewriteHandler.h"
      29             : #include "storage/bufmgr.h"
      30             : #include "tcop/tcopprot.h"
      31             : #include "utils/builtins.h"
      32             : #include "utils/guc_tables.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 ExplainPrintJIT(ExplainState *es, int jit_flags,
      61             :                             JitInstrumentation *ji);
      62             : static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
      63             :                             ExplainState *es);
      64             : static double elapsed_time(instr_time *starttime);
      65             : static bool ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used);
      66             : static void ExplainNode(PlanState *planstate, List *ancestors,
      67             :                         const char *relationship, const char *plan_name,
      68             :                         ExplainState *es);
      69             : static void show_plan_tlist(PlanState *planstate, List *ancestors,
      70             :                             ExplainState *es);
      71             : static void show_expression(Node *node, const char *qlabel,
      72             :                             PlanState *planstate, List *ancestors,
      73             :                             bool useprefix, ExplainState *es);
      74             : static void show_qual(List *qual, const char *qlabel,
      75             :                       PlanState *planstate, List *ancestors,
      76             :                       bool useprefix, ExplainState *es);
      77             : static void show_scan_qual(List *qual, const char *qlabel,
      78             :                            PlanState *planstate, List *ancestors,
      79             :                            ExplainState *es);
      80             : static void show_upper_qual(List *qual, const char *qlabel,
      81             :                             PlanState *planstate, List *ancestors,
      82             :                             ExplainState *es);
      83             : static void show_sort_keys(SortState *sortstate, List *ancestors,
      84             :                            ExplainState *es);
      85             : static void show_incremental_sort_keys(IncrementalSortState *incrsortstate,
      86             :                                        List *ancestors, ExplainState *es);
      87             : static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
      88             :                                    ExplainState *es);
      89             : static void show_agg_keys(AggState *astate, List *ancestors,
      90             :                           ExplainState *es);
      91             : static void show_grouping_sets(PlanState *planstate, Agg *agg,
      92             :                                List *ancestors, ExplainState *es);
      93             : static void show_grouping_set_keys(PlanState *planstate,
      94             :                                    Agg *aggnode, Sort *sortnode,
      95             :                                    List *context, bool useprefix,
      96             :                                    List *ancestors, ExplainState *es);
      97             : static void show_group_keys(GroupState *gstate, List *ancestors,
      98             :                             ExplainState *es);
      99             : static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
     100             :                                  int nkeys, int nPresortedKeys, AttrNumber *keycols,
     101             :                                  Oid *sortOperators, Oid *collations, bool *nullsFirst,
     102             :                                  List *ancestors, ExplainState *es);
     103             : static void show_sortorder_options(StringInfo buf, Node *sortexpr,
     104             :                                    Oid sortOperator, Oid collation, bool nullsFirst);
     105             : static void show_tablesample(TableSampleClause *tsc, PlanState *planstate,
     106             :                              List *ancestors, ExplainState *es);
     107             : static void show_sort_info(SortState *sortstate, ExplainState *es);
     108             : static void show_incremental_sort_info(IncrementalSortState *incrsortstate,
     109             :                                        ExplainState *es);
     110             : static void show_hash_info(HashState *hashstate, ExplainState *es);
     111             : static void show_hashagg_info(AggState *hashstate, ExplainState *es);
     112             : static void show_tidbitmap_info(BitmapHeapScanState *planstate,
     113             :                                 ExplainState *es);
     114             : static void show_instrumentation_count(const char *qlabel, int which,
     115             :                                        PlanState *planstate, ExplainState *es);
     116             : static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
     117             : static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
     118             : static const char *explain_get_index_name(Oid indexId);
     119             : static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
     120             : static void show_wal_usage(ExplainState *es, const WalUsage *usage);
     121             : static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
     122             :                                     ExplainState *es);
     123             : static void ExplainScanTarget(Scan *plan, ExplainState *es);
     124             : static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
     125             : static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
     126             : static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
     127             :                                   ExplainState *es);
     128             : static void ExplainMemberNodes(PlanState **planstates, int nplans,
     129             :                                List *ancestors, ExplainState *es);
     130             : static void ExplainMissingMembers(int nplans, int nchildren, ExplainState *es);
     131             : static void ExplainSubPlans(List *plans, List *ancestors,
     132             :                             const char *relationship, ExplainState *es);
     133             : static void ExplainCustomChildren(CustomScanState *css,
     134             :                                   List *ancestors, ExplainState *es);
     135             : static ExplainWorkersState *ExplainCreateWorkersState(int num_workers);
     136             : static void ExplainOpenWorker(int n, ExplainState *es);
     137             : static void ExplainCloseWorker(int n, ExplainState *es);
     138             : static void ExplainFlushWorkersState(ExplainState *es);
     139             : static void ExplainProperty(const char *qlabel, const char *unit,
     140             :                             const char *value, bool numeric, ExplainState *es);
     141             : static void ExplainOpenSetAsideGroup(const char *objtype, const char *labelname,
     142             :                                      bool labeled, int depth, ExplainState *es);
     143             : static void ExplainSaveGroup(ExplainState *es, int depth, int *state_save);
     144             : static void ExplainRestoreGroup(ExplainState *es, int depth, int *state_save);
     145             : static void ExplainDummyGroup(const char *objtype, const char *labelname,
     146             :                               ExplainState *es);
     147             : static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
     148             : static void ExplainIndentText(ExplainState *es);
     149             : static void ExplainJSONLineEnding(ExplainState *es);
     150             : static void ExplainYAMLLineStarting(ExplainState *es);
     151             : static void escape_yaml(StringInfo buf, const char *str);
     152             : 
     153             : 
     154             : 
     155             : /*
     156             :  * ExplainQuery -
     157             :  *    execute an EXPLAIN command
     158             :  */
     159             : void
     160       10398 : ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
     161             :              ParamListInfo params, DestReceiver *dest)
     162             : {
     163       10398 :     ExplainState *es = NewExplainState();
     164             :     TupOutputState *tstate;
     165             :     List       *rewritten;
     166             :     ListCell   *lc;
     167       10398 :     bool        timing_set = false;
     168       10398 :     bool        summary_set = false;
     169             : 
     170             :     /* Parse options list. */
     171       19184 :     foreach(lc, stmt->options)
     172             :     {
     173        8786 :         DefElem    *opt = (DefElem *) lfirst(lc);
     174             : 
     175        8786 :         if (strcmp(opt->defname, "analyze") == 0)
     176        1124 :             es->analyze = defGetBoolean(opt);
     177        7662 :         else if (strcmp(opt->defname, "verbose") == 0)
     178        1084 :             es->verbose = defGetBoolean(opt);
     179        6578 :         else if (strcmp(opt->defname, "costs") == 0)
     180        5814 :             es->costs = defGetBoolean(opt);
     181         764 :         else if (strcmp(opt->defname, "buffers") == 0)
     182          20 :             es->buffers = defGetBoolean(opt);
     183         744 :         else if (strcmp(opt->defname, "wal") == 0)
     184           0 :             es->wal = defGetBoolean(opt);
     185         744 :         else if (strcmp(opt->defname, "settings") == 0)
     186           8 :             es->settings = defGetBoolean(opt);
     187         736 :         else if (strcmp(opt->defname, "timing") == 0)
     188             :         {
     189         296 :             timing_set = true;
     190         296 :             es->timing = defGetBoolean(opt);
     191             :         }
     192         440 :         else if (strcmp(opt->defname, "summary") == 0)
     193             :         {
     194         284 :             summary_set = true;
     195         284 :             es->summary = defGetBoolean(opt);
     196             :         }
     197         156 :         else if (strcmp(opt->defname, "format") == 0)
     198             :         {
     199         156 :             char       *p = defGetString(opt);
     200             : 
     201         156 :             if (strcmp(p, "text") == 0)
     202           4 :                 es->format = EXPLAIN_FORMAT_TEXT;
     203         152 :             else if (strcmp(p, "xml") == 0)
     204           4 :                 es->format = EXPLAIN_FORMAT_XML;
     205         148 :             else if (strcmp(p, "json") == 0)
     206         144 :                 es->format = EXPLAIN_FORMAT_JSON;
     207           4 :             else if (strcmp(p, "yaml") == 0)
     208           4 :                 es->format = EXPLAIN_FORMAT_YAML;
     209             :             else
     210           0 :                 ereport(ERROR,
     211             :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     212             :                          errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
     213             :                                 opt->defname, p),
     214             :                          parser_errposition(pstate, opt->location)));
     215             :         }
     216             :         else
     217           0 :             ereport(ERROR,
     218             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     219             :                      errmsg("unrecognized EXPLAIN option \"%s\"",
     220             :                             opt->defname),
     221             :                      parser_errposition(pstate, opt->location)));
     222             :     }
     223             : 
     224       10398 :     if (es->buffers && !es->analyze)
     225           0 :         ereport(ERROR,
     226             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     227             :                  errmsg("EXPLAIN option BUFFERS requires ANALYZE")));
     228             : 
     229       10398 :     if (es->wal && !es->analyze)
     230           0 :         ereport(ERROR,
     231             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     232             :                  errmsg("EXPLAIN option WAL requires ANALYZE")));
     233             : 
     234             :     /* if the timing was not set explicitly, set default value */
     235       10398 :     es->timing = (timing_set) ? es->timing : es->analyze;
     236             : 
     237             :     /* check that timing is used with EXPLAIN ANALYZE */
     238       10398 :     if (es->timing && !es->analyze)
     239           0 :         ereport(ERROR,
     240             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     241             :                  errmsg("EXPLAIN option TIMING requires ANALYZE")));
     242             : 
     243             :     /* if the summary was not set explicitly, set default value */
     244       10398 :     es->summary = (summary_set) ? es->summary : es->analyze;
     245             : 
     246             :     /*
     247             :      * Parse analysis was done already, but we still have to run the rule
     248             :      * rewriter.  We do not do AcquireRewriteLocks: we assume the query either
     249             :      * came straight from the parser, or suitable locks were acquired by
     250             :      * plancache.c.
     251             :      *
     252             :      * Because the rewriter and planner tend to scribble on the input, we make
     253             :      * a preliminary copy of the source querytree.  This prevents problems in
     254             :      * the case that the EXPLAIN is in a portal or plpgsql function and is
     255             :      * executed repeatedly.  (See also the same hack in DECLARE CURSOR and
     256             :      * PREPARE.)  XXX FIXME someday.
     257             :      */
     258       10398 :     rewritten = QueryRewrite(castNode(Query, copyObject(stmt->query)));
     259             : 
     260             :     /* emit opening boilerplate */
     261       10398 :     ExplainBeginOutput(es);
     262             : 
     263       10398 :     if (rewritten == NIL)
     264             :     {
     265             :         /*
     266             :          * In the case of an INSTEAD NOTHING, tell at least that.  But in
     267             :          * non-text format, the output is delimited, so this isn't necessary.
     268             :          */
     269           0 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     270           0 :             appendStringInfoString(es->str, "Query rewrites to nothing\n");
     271             :     }
     272             :     else
     273             :     {
     274             :         ListCell   *l;
     275             : 
     276             :         /* Explain every plan */
     277       20776 :         foreach(l, rewritten)
     278             :         {
     279       10406 :             ExplainOneQuery(lfirst_node(Query, l),
     280             :                             CURSOR_OPT_PARALLEL_OK, NULL, es,
     281             :                             pstate->p_sourcetext, params, pstate->p_queryEnv);
     282             : 
     283             :             /* Separate plans with an appropriate separator */
     284       10378 :             if (lnext(rewritten, l) != NULL)
     285           8 :                 ExplainSeparatePlans(es);
     286             :         }
     287             :     }
     288             : 
     289             :     /* emit closing boilerplate */
     290       10370 :     ExplainEndOutput(es);
     291             :     Assert(es->indent == 0);
     292             : 
     293             :     /* output tuples */
     294       10370 :     tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt),
     295             :                                       &TTSOpsVirtual);
     296       10370 :     if (es->format == EXPLAIN_FORMAT_TEXT)
     297       10218 :         do_text_output_multiline(tstate, es->str->data);
     298             :     else
     299         152 :         do_text_output_oneline(tstate, es->str->data);
     300       10370 :     end_tup_output(tstate);
     301             : 
     302       10370 :     pfree(es->str->data);
     303       10370 : }
     304             : 
     305             : /*
     306             :  * Create a new ExplainState struct initialized with default options.
     307             :  */
     308             : ExplainState *
     309       10398 : NewExplainState(void)
     310             : {
     311       10398 :     ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));
     312             : 
     313             :     /* Set default options (most fields can be left as zeroes). */
     314       10398 :     es->costs = true;
     315             :     /* Prepare output buffer. */
     316       10398 :     es->str = makeStringInfo();
     317             : 
     318       10398 :     return es;
     319             : }
     320             : 
     321             : /*
     322             :  * ExplainResultDesc -
     323             :  *    construct the result tupledesc for an EXPLAIN
     324             :  */
     325             : TupleDesc
     326       23668 : ExplainResultDesc(ExplainStmt *stmt)
     327             : {
     328             :     TupleDesc   tupdesc;
     329             :     ListCell   *lc;
     330       23668 :     Oid         result_type = TEXTOID;
     331             : 
     332             :     /* Check for XML format option */
     333       42468 :     foreach(lc, stmt->options)
     334             :     {
     335       18800 :         DefElem    *opt = (DefElem *) lfirst(lc);
     336             : 
     337       18800 :         if (strcmp(opt->defname, "format") == 0)
     338             :         {
     339         448 :             char       *p = defGetString(opt);
     340             : 
     341         448 :             if (strcmp(p, "xml") == 0)
     342          12 :                 result_type = XMLOID;
     343         436 :             else if (strcmp(p, "json") == 0)
     344         412 :                 result_type = JSONOID;
     345             :             else
     346          24 :                 result_type = TEXTOID;
     347             :             /* don't "break", as ExplainQuery will use the last value */
     348             :         }
     349             :     }
     350             : 
     351             :     /* Need a tuple descriptor representing a single TEXT or XML column */
     352       23668 :     tupdesc = CreateTemplateTupleDesc(1);
     353       23668 :     TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
     354             :                        result_type, -1, 0);
     355       23668 :     return tupdesc;
     356             : }
     357             : 
     358             : /*
     359             :  * ExplainOneQuery -
     360             :  *    print out the execution plan for one Query
     361             :  *
     362             :  * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
     363             :  */
     364             : static void
     365       10470 : ExplainOneQuery(Query *query, int cursorOptions,
     366             :                 IntoClause *into, ExplainState *es,
     367             :                 const char *queryString, ParamListInfo params,
     368             :                 QueryEnvironment *queryEnv)
     369             : {
     370             :     /* planner will not cope with utility statements */
     371       10470 :     if (query->commandType == CMD_UTILITY)
     372             :     {
     373         310 :         ExplainOneUtility(query->utilityStmt, into, es, queryString, params,
     374             :                           queryEnv);
     375         310 :         return;
     376             :     }
     377             : 
     378             :     /* if an advisor plugin is present, let it manage things */
     379       10160 :     if (ExplainOneQuery_hook)
     380           0 :         (*ExplainOneQuery_hook) (query, cursorOptions, into, es,
     381             :                                  queryString, params, queryEnv);
     382             :     else
     383             :     {
     384             :         PlannedStmt *plan;
     385             :         instr_time  planstart,
     386             :                     planduration;
     387             :         BufferUsage bufusage_start,
     388             :                     bufusage;
     389             : 
     390       10160 :         if (es->buffers)
     391          20 :             bufusage_start = pgBufferUsage;
     392       10160 :         INSTR_TIME_SET_CURRENT(planstart);
     393             : 
     394             :         /* plan the query */
     395       10160 :         plan = pg_plan_query(query, queryString, cursorOptions, params);
     396             : 
     397       10144 :         INSTR_TIME_SET_CURRENT(planduration);
     398       10150 :         INSTR_TIME_SUBTRACT(planduration, planstart);
     399             : 
     400             :         /* calc differences of buffer counters. */
     401       10144 :         if (es->buffers)
     402             :         {
     403          20 :             memset(&bufusage, 0, sizeof(BufferUsage));
     404          20 :             BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start);
     405             :         }
     406             : 
     407             :         /* run it (if needed) and produce output */
     408       10144 :         ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
     409       10144 :                        &planduration, (es->buffers ? &bufusage : NULL));
     410             :     }
     411             : }
     412             : 
     413             : /*
     414             :  * ExplainOneUtility -
     415             :  *    print out the execution plan for one utility statement
     416             :  *    (In general, utility statements don't have plans, but there are some
     417             :  *    we treat as special cases)
     418             :  *
     419             :  * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
     420             :  *
     421             :  * This is exported because it's called back from prepare.c in the
     422             :  * EXPLAIN EXECUTE case.
     423             :  */
     424             : void
     425         310 : ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
     426             :                   const char *queryString, ParamListInfo params,
     427             :                   QueryEnvironment *queryEnv)
     428             : {
     429         310 :     if (utilityStmt == NULL)
     430           0 :         return;
     431             : 
     432         310 :     if (IsA(utilityStmt, CreateTableAsStmt))
     433             :     {
     434             :         /*
     435             :          * We have to rewrite the contained SELECT and then pass it back to
     436             :          * ExplainOneQuery.  It's probably not really necessary to copy the
     437             :          * contained parsetree another time, but let's be safe.
     438             :          */
     439          36 :         CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
     440             :         List       *rewritten;
     441             : 
     442          36 :         rewritten = QueryRewrite(castNode(Query, copyObject(ctas->query)));
     443             :         Assert(list_length(rewritten) == 1);
     444          36 :         ExplainOneQuery(linitial_node(Query, rewritten),
     445             :                         CURSOR_OPT_PARALLEL_OK, ctas->into, es,
     446             :                         queryString, params, queryEnv);
     447             :     }
     448         274 :     else if (IsA(utilityStmt, DeclareCursorStmt))
     449             :     {
     450             :         /*
     451             :          * Likewise for DECLARE CURSOR.
     452             :          *
     453             :          * Notice that if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll
     454             :          * actually run the query.  This is different from pre-8.3 behavior
     455             :          * but seems more useful than not running the query.  No cursor will
     456             :          * be created, however.
     457             :          */
     458          28 :         DeclareCursorStmt *dcs = (DeclareCursorStmt *) utilityStmt;
     459             :         List       *rewritten;
     460             : 
     461          28 :         rewritten = QueryRewrite(castNode(Query, copyObject(dcs->query)));
     462             :         Assert(list_length(rewritten) == 1);
     463          28 :         ExplainOneQuery(linitial_node(Query, rewritten),
     464             :                         dcs->options, NULL, es,
     465             :                         queryString, params, queryEnv);
     466             :     }
     467         246 :     else if (IsA(utilityStmt, ExecuteStmt))
     468         246 :         ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
     469             :                             queryString, params, queryEnv);
     470           0 :     else if (IsA(utilityStmt, NotifyStmt))
     471             :     {
     472           0 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     473           0 :             appendStringInfoString(es->str, "NOTIFY\n");
     474             :         else
     475           0 :             ExplainDummyGroup("Notify", NULL, es);
     476             :     }
     477             :     else
     478             :     {
     479           0 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     480           0 :             appendStringInfoString(es->str,
     481             :                                    "Utility statements have no plan structure\n");
     482             :         else
     483           0 :             ExplainDummyGroup("Utility Statement", NULL, es);
     484             :     }
     485             : }
     486             : 
     487             : /*
     488             :  * ExplainOnePlan -
     489             :  *      given a planned query, execute it if needed, and then print
     490             :  *      EXPLAIN output
     491             :  *
     492             :  * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt,
     493             :  * in which case executing the query should result in creating that table.
     494             :  *
     495             :  * This is exported because it's called back from prepare.c in the
     496             :  * EXPLAIN EXECUTE case, and because an index advisor plugin would need
     497             :  * to call it.
     498             :  */
     499             : void
     500       10390 : ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
     501             :                const char *queryString, ParamListInfo params,
     502             :                QueryEnvironment *queryEnv, const instr_time *planduration,
     503             :                const BufferUsage *bufusage)
     504             : {
     505             :     DestReceiver *dest;
     506             :     QueryDesc  *queryDesc;
     507             :     instr_time  starttime;
     508       10390 :     double      totaltime = 0;
     509             :     int         eflags;
     510       10390 :     int         instrument_option = 0;
     511             : 
     512             :     Assert(plannedstmt->commandType != CMD_UTILITY);
     513             : 
     514       10390 :     if (es->analyze && es->timing)
     515         840 :         instrument_option |= INSTRUMENT_TIMER;
     516        9550 :     else if (es->analyze)
     517         284 :         instrument_option |= INSTRUMENT_ROWS;
     518             : 
     519       10390 :     if (es->buffers)
     520          20 :         instrument_option |= INSTRUMENT_BUFFERS;
     521       10390 :     if (es->wal)
     522           0 :         instrument_option |= INSTRUMENT_WAL;
     523             : 
     524             :     /*
     525             :      * We always collect timing for the entire statement, even when node-level
     526             :      * timing is off, so we don't look at es->timing here.  (We could skip
     527             :      * this if !es->summary, but it's hardly worth the complication.)
     528             :      */
     529       10390 :     INSTR_TIME_SET_CURRENT(starttime);
     530             : 
     531             :     /*
     532             :      * Use a snapshot with an updated command ID to ensure this query sees
     533             :      * results of any previously executed queries.
     534             :      */
     535       10390 :     PushCopiedSnapshot(GetActiveSnapshot());
     536       10390 :     UpdateActiveSnapshotCommandId();
     537             : 
     538             :     /*
     539             :      * Normally we discard the query's output, but if explaining CREATE TABLE
     540             :      * AS, we'd better use the appropriate tuple receiver.
     541             :      */
     542       10390 :     if (into)
     543          36 :         dest = CreateIntoRelDestReceiver(into);
     544             :     else
     545       10354 :         dest = None_Receiver;
     546             : 
     547             :     /* Create a QueryDesc for the query */
     548       10390 :     queryDesc = CreateQueryDesc(plannedstmt, queryString,
     549             :                                 GetActiveSnapshot(), InvalidSnapshot,
     550             :                                 dest, params, queryEnv, instrument_option);
     551             : 
     552             :     /* Select execution options */
     553       10390 :     if (es->analyze)
     554        1124 :         eflags = 0;             /* default run-to-completion flags */
     555             :     else
     556        9266 :         eflags = EXEC_FLAG_EXPLAIN_ONLY;
     557       10390 :     if (into)
     558          36 :         eflags |= GetIntoRelEFlags(into);
     559             : 
     560             :     /* call ExecutorStart to prepare the plan for execution */
     561       10390 :     ExecutorStart(queryDesc, eflags);
     562             : 
     563             :     /* Execute the plan for statistics if asked for */
     564       10382 :     if (es->analyze)
     565             :     {
     566             :         ScanDirection dir;
     567             : 
     568             :         /* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */
     569        1124 :         if (into && into->skipData)
     570           4 :             dir = NoMovementScanDirection;
     571             :         else
     572        1120 :             dir = ForwardScanDirection;
     573             : 
     574             :         /* run the plan */
     575        1124 :         ExecutorRun(queryDesc, dir, 0L, true);
     576             : 
     577             :         /* run cleanup too */
     578        1120 :         ExecutorFinish(queryDesc);
     579             : 
     580             :         /* We can't run ExecutorEnd 'till we're done printing the stats... */
     581        1120 :         totaltime += elapsed_time(&starttime);
     582             :     }
     583             : 
     584       10378 :     ExplainOpenGroup("Query", NULL, true, es);
     585             : 
     586             :     /* Create textual dump of plan tree */
     587       10378 :     ExplainPrintPlan(es, queryDesc);
     588             : 
     589       10378 :     if (es->summary && (planduration || bufusage))
     590         840 :         ExplainOpenGroup("Planning", "Planning", true, es);
     591             : 
     592       10378 :     if (es->summary && planduration)
     593             :     {
     594         840 :         double      plantime = INSTR_TIME_GET_DOUBLE(*planduration);
     595             : 
     596         840 :         ExplainPropertyFloat("Planning Time", "ms", 1000.0 * plantime, 3, es);
     597             :     }
     598             : 
     599             :     /* Show buffer usage */
     600       10378 :     if (es->summary && bufusage)
     601             :     {
     602          20 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     603           4 :             es->indent++;
     604          20 :         show_buffer_usage(es, bufusage);
     605          20 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     606           4 :             es->indent--;
     607             :     }
     608             : 
     609       10378 :     if (es->summary && (planduration || bufusage))
     610         840 :         ExplainCloseGroup("Planning", "Planning", true, es);
     611             : 
     612             :     /* Print info about runtime of triggers */
     613       10378 :     if (es->analyze)
     614        1120 :         ExplainPrintTriggers(es, queryDesc);
     615             : 
     616             :     /*
     617             :      * Print info about JITing. Tied to es->costs because we don't want to
     618             :      * display this in regression tests, as it'd cause output differences
     619             :      * depending on build options.  Might want to separate that out from COSTS
     620             :      * at a later stage.
     621             :      */
     622       10378 :     if (es->costs)
     623        4580 :         ExplainPrintJITSummary(es, queryDesc);
     624             : 
     625             :     /*
     626             :      * Close down the query and free resources.  Include time for this in the
     627             :      * total execution time (although it should be pretty minimal).
     628             :      */
     629       10378 :     INSTR_TIME_SET_CURRENT(starttime);
     630             : 
     631       10378 :     ExecutorEnd(queryDesc);
     632             : 
     633       10378 :     FreeQueryDesc(queryDesc);
     634             : 
     635       10378 :     PopActiveSnapshot();
     636             : 
     637             :     /* We need a CCI just in case query expanded to multiple plans */
     638       10378 :     if (es->analyze)
     639        1120 :         CommandCounterIncrement();
     640             : 
     641       10378 :     totaltime += elapsed_time(&starttime);
     642             : 
     643             :     /*
     644             :      * We only report execution time if we actually ran the query (that is,
     645             :      * the user specified ANALYZE), and if summary reporting is enabled (the
     646             :      * user can set SUMMARY OFF to not have the timing information included in
     647             :      * the output).  By default, ANALYZE sets SUMMARY to true.
     648             :      */
     649       10378 :     if (es->summary && es->analyze)
     650         840 :         ExplainPropertyFloat("Execution Time", "ms", 1000.0 * totaltime, 3,
     651             :                              es);
     652             : 
     653       10378 :     ExplainCloseGroup("Query", NULL, true, es);
     654       10378 : }
     655             : 
     656             : /*
     657             :  * ExplainPrintSettings -
     658             :  *    Print summary of modified settings affecting query planning.
     659             :  */
     660             : static void
     661       10378 : ExplainPrintSettings(ExplainState *es)
     662             : {
     663             :     int         num;
     664             :     struct config_generic **gucs;
     665             : 
     666             :     /* bail out if information about settings not requested */
     667       10378 :     if (!es->settings)
     668       10370 :         return;
     669             : 
     670             :     /* request an array of relevant settings */
     671           8 :     gucs = get_explain_guc_options(&num);
     672             : 
     673           8 :     if (es->format != EXPLAIN_FORMAT_TEXT)
     674             :     {
     675           4 :         ExplainOpenGroup("Settings", "Settings", true, es);
     676             : 
     677           8 :         for (int i = 0; i < num; i++)
     678             :         {
     679             :             char       *setting;
     680           4 :             struct config_generic *conf = gucs[i];
     681             : 
     682           4 :             setting = GetConfigOptionByName(conf->name, NULL, true);
     683             : 
     684           4 :             ExplainPropertyText(conf->name, setting, es);
     685             :         }
     686             : 
     687           4 :         ExplainCloseGroup("Settings", "Settings", true, es);
     688             :     }
     689             :     else
     690             :     {
     691             :         StringInfoData str;
     692             : 
     693             :         /* In TEXT mode, print nothing if there are no options */
     694           4 :         if (num <= 0)
     695           0 :             return;
     696             : 
     697           4 :         initStringInfo(&str);
     698             : 
     699           8 :         for (int i = 0; i < num; i++)
     700             :         {
     701             :             char       *setting;
     702           4 :             struct config_generic *conf = gucs[i];
     703             : 
     704           4 :             if (i > 0)
     705           0 :                 appendStringInfoString(&str, ", ");
     706             : 
     707           4 :             setting = GetConfigOptionByName(conf->name, NULL, true);
     708             : 
     709           4 :             if (setting)
     710           4 :                 appendStringInfo(&str, "%s = '%s'", conf->name, setting);
     711             :             else
     712           0 :                 appendStringInfo(&str, "%s = NULL", conf->name);
     713             :         }
     714             : 
     715           4 :         ExplainPropertyText("Settings", str.data, es);
     716             :     }
     717             : }
     718             : 
     719             : /*
     720             :  * ExplainPrintPlan -
     721             :  *    convert a QueryDesc's plan tree to text and append it to es->str
     722             :  *
     723             :  * The caller should have set up the options fields of *es, as well as
     724             :  * initializing the output buffer es->str.  Also, output formatting state
     725             :  * such as the indent level is assumed valid.  Plan-tree-specific fields
     726             :  * in *es are initialized here.
     727             :  *
     728             :  * NB: will not work on utility statements
     729             :  */
     730             : void
     731       10378 : ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
     732             : {
     733       10378 :     Bitmapset  *rels_used = NULL;
     734             :     PlanState  *ps;
     735             : 
     736             :     /* Set up ExplainState fields associated with this plan tree */
     737             :     Assert(queryDesc->plannedstmt != NULL);
     738       10378 :     es->pstmt = queryDesc->plannedstmt;
     739       10378 :     es->rtable = queryDesc->plannedstmt->rtable;
     740       10378 :     ExplainPreScanNode(queryDesc->planstate, &rels_used);
     741       10378 :     es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used);
     742       10378 :     es->deparse_cxt = deparse_context_for_plan_tree(queryDesc->plannedstmt,
     743             :                                                     es->rtable_names);
     744       10378 :     es->printed_subplans = NULL;
     745             : 
     746             :     /*
     747             :      * Sometimes we mark a Gather node as "invisible", which means that it's
     748             :      * not to be displayed in EXPLAIN output.  The purpose of this is to allow
     749             :      * running regression tests with force_parallel_mode=regress to get the
     750             :      * same results as running the same tests with force_parallel_mode=off.
     751             :      * Such marking is currently only supported on a Gather at the top of the
     752             :      * plan.  We skip that node, and we must also hide per-worker detail data
     753             :      * further down in the plan tree.
     754             :      */
     755       10378 :     ps = queryDesc->planstate;
     756       10378 :     if (IsA(ps, GatherState) && ((Gather *) ps->plan)->invisible)
     757             :     {
     758           0 :         ps = outerPlanState(ps);
     759           0 :         es->hide_workers = true;
     760             :     }
     761       10378 :     ExplainNode(ps, NIL, NULL, NULL, es);
     762             : 
     763             :     /*
     764             :      * If requested, include information about GUC parameters with values that
     765             :      * don't match the built-in defaults.
     766             :      */
     767       10378 :     ExplainPrintSettings(es);
     768       10378 : }
     769             : 
     770             : /*
     771             :  * ExplainPrintTriggers -
     772             :  *    convert a QueryDesc's trigger statistics to text and append it to
     773             :  *    es->str
     774             :  *
     775             :  * The caller should have set up the options fields of *es, as well as
     776             :  * initializing the output buffer es->str.  Other fields in *es are
     777             :  * initialized here.
     778             :  */
     779             : void
     780        1120 : ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
     781             : {
     782             :     ResultRelInfo *rInfo;
     783             :     bool        show_relname;
     784        1120 :     int         numrels = queryDesc->estate->es_num_result_relations;
     785        1120 :     int         numrootrels = queryDesc->estate->es_num_root_result_relations;
     786             :     List       *routerels;
     787             :     List       *targrels;
     788             :     int         nr;
     789             :     ListCell   *l;
     790             : 
     791        1120 :     routerels = queryDesc->estate->es_tuple_routing_result_relations;
     792        1120 :     targrels = queryDesc->estate->es_trig_target_relations;
     793             : 
     794        1120 :     ExplainOpenGroup("Triggers", "Triggers", false, es);
     795             : 
     796        1112 :     show_relname = (numrels > 1 || numrootrels > 0 ||
     797        2232 :                     routerels != NIL || targrels != NIL);
     798        1120 :     rInfo = queryDesc->estate->es_result_relations;
     799        1152 :     for (nr = 0; nr < numrels; rInfo++, nr++)
     800          32 :         report_triggers(rInfo, show_relname, es);
     801             : 
     802        1120 :     rInfo = queryDesc->estate->es_root_result_relations;
     803        1128 :     for (nr = 0; nr < numrootrels; rInfo++, nr++)
     804           8 :         report_triggers(rInfo, show_relname, es);
     805             : 
     806        1120 :     foreach(l, routerels)
     807             :     {
     808           0 :         rInfo = (ResultRelInfo *) lfirst(l);
     809           0 :         report_triggers(rInfo, show_relname, es);
     810             :     }
     811             : 
     812        1120 :     foreach(l, targrels)
     813             :     {
     814           0 :         rInfo = (ResultRelInfo *) lfirst(l);
     815           0 :         report_triggers(rInfo, show_relname, es);
     816             :     }
     817             : 
     818        1120 :     ExplainCloseGroup("Triggers", "Triggers", false, es);
     819        1120 : }
     820             : 
     821             : /*
     822             :  * ExplainPrintJITSummary -
     823             :  *    Print summarized JIT instrumentation from leader and workers
     824             :  */
     825             : void
     826        4580 : ExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc)
     827             : {
     828        4580 :     JitInstrumentation ji = {0};
     829             : 
     830        4580 :     if (!(queryDesc->estate->es_jit_flags & PGJIT_PERFORM))
     831        4560 :         return;
     832             : 
     833             :     /*
     834             :      * Work with a copy instead of modifying the leader state, since this
     835             :      * function may be called twice
     836             :      */
     837          20 :     if (queryDesc->estate->es_jit)
     838          20 :         InstrJitAgg(&ji, &queryDesc->estate->es_jit->instr);
     839             : 
     840             :     /* If this process has done JIT in parallel workers, merge stats */
     841          20 :     if (queryDesc->estate->es_jit_worker_instr)
     842          16 :         InstrJitAgg(&ji, queryDesc->estate->es_jit_worker_instr);
     843             : 
     844          20 :     ExplainPrintJIT(es, queryDesc->estate->es_jit_flags, &ji);
     845             : }
     846             : 
     847             : /*
     848             :  * ExplainPrintJIT -
     849             :  *    Append information about JITing to es->str.
     850             :  */
     851             : static void
     852          20 : ExplainPrintJIT(ExplainState *es, int jit_flags, JitInstrumentation *ji)
     853             : {
     854             :     instr_time  total_time;
     855             : 
     856             :     /* don't print information if no JITing happened */
     857          20 :     if (!ji || ji->created_functions == 0)
     858           0 :         return;
     859             : 
     860             :     /* calculate total time */
     861          20 :     INSTR_TIME_SET_ZERO(total_time);
     862          20 :     INSTR_TIME_ADD(total_time, ji->generation_counter);
     863          20 :     INSTR_TIME_ADD(total_time, ji->inlining_counter);
     864          20 :     INSTR_TIME_ADD(total_time, ji->optimization_counter);
     865          20 :     INSTR_TIME_ADD(total_time, ji->emission_counter);
     866             : 
     867          20 :     ExplainOpenGroup("JIT", "JIT", true, es);
     868             : 
     869             :     /* for higher density, open code the text output format */
     870          20 :     if (es->format == EXPLAIN_FORMAT_TEXT)
     871             :     {
     872           4 :         ExplainIndentText(es);
     873           4 :         appendStringInfoString(es->str, "JIT:\n");
     874           4 :         es->indent++;
     875             : 
     876           4 :         ExplainPropertyInteger("Functions", NULL, ji->created_functions, es);
     877             : 
     878           4 :         ExplainIndentText(es);
     879          16 :         appendStringInfo(es->str, "Options: %s %s, %s %s, %s %s, %s %s\n",
     880           4 :                          "Inlining", jit_flags & PGJIT_INLINE ? "true" : "false",
     881           4 :                          "Optimization", jit_flags & PGJIT_OPT3 ? "true" : "false",
     882           4 :                          "Expressions", jit_flags & PGJIT_EXPR ? "true" : "false",
     883           4 :                          "Deforming", jit_flags & PGJIT_DEFORM ? "true" : "false");
     884             : 
     885           4 :         if (es->analyze && es->timing)
     886             :         {
     887           0 :             ExplainIndentText(es);
     888           0 :             appendStringInfo(es->str,
     889             :                              "Timing: %s %.3f ms, %s %.3f ms, %s %.3f ms, %s %.3f ms, %s %.3f ms\n",
     890           0 :                              "Generation", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->generation_counter),
     891           0 :                              "Inlining", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->inlining_counter),
     892           0 :                              "Optimization", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->optimization_counter),
     893           0 :                              "Emission", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->emission_counter),
     894           0 :                              "Total", 1000.0 * INSTR_TIME_GET_DOUBLE(total_time));
     895             :         }
     896             : 
     897           4 :         es->indent--;
     898             :     }
     899             :     else
     900             :     {
     901          16 :         ExplainPropertyInteger("Functions", NULL, ji->created_functions, es);
     902             : 
     903          16 :         ExplainOpenGroup("Options", "Options", true, es);
     904          16 :         ExplainPropertyBool("Inlining", jit_flags & PGJIT_INLINE, es);
     905          16 :         ExplainPropertyBool("Optimization", jit_flags & PGJIT_OPT3, es);
     906          16 :         ExplainPropertyBool("Expressions", jit_flags & PGJIT_EXPR, es);
     907          16 :         ExplainPropertyBool("Deforming", jit_flags & PGJIT_DEFORM, es);
     908          16 :         ExplainCloseGroup("Options", "Options", true, es);
     909             : 
     910          16 :         if (es->analyze && es->timing)
     911             :         {
     912          16 :             ExplainOpenGroup("Timing", "Timing", true, es);
     913             : 
     914          16 :             ExplainPropertyFloat("Generation", "ms",
     915          16 :                                  1000.0 * INSTR_TIME_GET_DOUBLE(ji->generation_counter),
     916             :                                  3, es);
     917          16 :             ExplainPropertyFloat("Inlining", "ms",
     918          16 :                                  1000.0 * INSTR_TIME_GET_DOUBLE(ji->inlining_counter),
     919             :                                  3, es);
     920          16 :             ExplainPropertyFloat("Optimization", "ms",
     921          16 :                                  1000.0 * INSTR_TIME_GET_DOUBLE(ji->optimization_counter),
     922             :                                  3, es);
     923          16 :             ExplainPropertyFloat("Emission", "ms",
     924          16 :                                  1000.0 * INSTR_TIME_GET_DOUBLE(ji->emission_counter),
     925             :                                  3, es);
     926          16 :             ExplainPropertyFloat("Total", "ms",
     927          16 :                                  1000.0 * INSTR_TIME_GET_DOUBLE(total_time),
     928             :                                  3, es);
     929             : 
     930          16 :             ExplainCloseGroup("Timing", "Timing", true, es);
     931             :         }
     932             :     }
     933             : 
     934          20 :     ExplainCloseGroup("JIT", "JIT", true, es);
     935             : }
     936             : 
     937             : /*
     938             :  * ExplainQueryText -
     939             :  *    add a "Query Text" node that contains the actual text of the query
     940             :  *
     941             :  * The caller should have set up the options fields of *es, as well as
     942             :  * initializing the output buffer es->str.
     943             :  *
     944             :  */
     945             : void
     946           0 : ExplainQueryText(ExplainState *es, QueryDesc *queryDesc)
     947             : {
     948           0 :     if (queryDesc->sourceText)
     949           0 :         ExplainPropertyText("Query Text", queryDesc->sourceText, es);
     950           0 : }
     951             : 
     952             : /*
     953             :  * report_triggers -
     954             :  *      report execution stats for a single relation's triggers
     955             :  */
     956             : static void
     957          40 : report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es)
     958             : {
     959             :     int         nt;
     960             : 
     961          40 :     if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
     962          40 :         return;
     963           0 :     for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
     964             :     {
     965           0 :         Trigger    *trig = rInfo->ri_TrigDesc->triggers + nt;
     966           0 :         Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
     967             :         char       *relname;
     968           0 :         char       *conname = NULL;
     969             : 
     970             :         /* Must clean up instrumentation state */
     971           0 :         InstrEndLoop(instr);
     972             : 
     973             :         /*
     974             :          * We ignore triggers that were never invoked; they likely aren't
     975             :          * relevant to the current query type.
     976             :          */
     977           0 :         if (instr->ntuples == 0)
     978           0 :             continue;
     979             : 
     980           0 :         ExplainOpenGroup("Trigger", NULL, true, es);
     981             : 
     982           0 :         relname = RelationGetRelationName(rInfo->ri_RelationDesc);
     983           0 :         if (OidIsValid(trig->tgconstraint))
     984           0 :             conname = get_constraint_name(trig->tgconstraint);
     985             : 
     986             :         /*
     987             :          * In text format, we avoid printing both the trigger name and the
     988             :          * constraint name unless VERBOSE is specified.  In non-text formats
     989             :          * we just print everything.
     990             :          */
     991           0 :         if (es->format == EXPLAIN_FORMAT_TEXT)
     992             :         {
     993           0 :             if (es->verbose || conname == NULL)
     994           0 :                 appendStringInfo(es->str, "Trigger %s", trig->tgname);
     995             :             else
     996           0 :                 appendStringInfoString(es->str, "Trigger");
     997           0 :             if (conname)
     998           0 :                 appendStringInfo(es->str, " for constraint %s", conname);
     999           0 :             if (show_relname)
    1000           0 :                 appendStringInfo(es->str, " on %s", relname);
    1001           0 :             if (es->timing)
    1002           0 :                 appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",
    1003           0 :                                  1000.0 * instr->total, instr->ntuples);
    1004             :             else
    1005           0 :                 appendStringInfo(es->str, ": calls=%.0f\n", instr->ntuples);
    1006             :         }
    1007             :         else
    1008             :         {
    1009           0 :             ExplainPropertyText("Trigger Name", trig->tgname, es);
    1010           0 :             if (conname)
    1011           0 :                 ExplainPropertyText("Constraint Name", conname, es);
    1012           0 :             ExplainPropertyText("Relation", relname, es);
    1013           0 :             if (es->timing)
    1014           0 :                 ExplainPropertyFloat("Time", "ms", 1000.0 * instr->total, 3,
    1015             :                                      es);
    1016           0 :             ExplainPropertyFloat("Calls", NULL, instr->ntuples, 0, es);
    1017             :         }
    1018             : 
    1019           0 :         if (conname)
    1020           0 :             pfree(conname);
    1021             : 
    1022           0 :         ExplainCloseGroup("Trigger", NULL, true, es);
    1023             :     }
    1024             : }
    1025             : 
    1026             : /* Compute elapsed time in seconds since given timestamp */
    1027             : static double
    1028       11498 : elapsed_time(instr_time *starttime)
    1029             : {
    1030             :     instr_time  endtime;
    1031             : 
    1032       11498 :     INSTR_TIME_SET_CURRENT(endtime);
    1033       11540 :     INSTR_TIME_SUBTRACT(endtime, *starttime);
    1034       11498 :     return INSTR_TIME_GET_DOUBLE(endtime);
    1035             : }
    1036             : 
    1037             : /*
    1038             :  * ExplainPreScanNode -
    1039             :  *    Prescan the planstate tree to identify which RTEs are referenced
    1040             :  *
    1041             :  * Adds the relid of each referenced RTE to *rels_used.  The result controls
    1042             :  * which RTEs are assigned aliases by select_rtable_names_for_explain.
    1043             :  * This ensures that we don't confusingly assign un-suffixed aliases to RTEs
    1044             :  * that never appear in the EXPLAIN output (such as inheritance parents).
    1045             :  */
    1046             : static bool
    1047       38172 : ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
    1048             : {
    1049       38172 :     Plan       *plan = planstate->plan;
    1050             : 
    1051       38172 :     switch (nodeTag(plan))
    1052             :     {
    1053       18200 :         case T_SeqScan:
    1054             :         case T_SampleScan:
    1055             :         case T_IndexScan:
    1056             :         case T_IndexOnlyScan:
    1057             :         case T_BitmapHeapScan:
    1058             :         case T_TidScan:
    1059             :         case T_SubqueryScan:
    1060             :         case T_FunctionScan:
    1061             :         case T_TableFuncScan:
    1062             :         case T_ValuesScan:
    1063             :         case T_CteScan:
    1064             :         case T_NamedTuplestoreScan:
    1065             :         case T_WorkTableScan:
    1066       36400 :             *rels_used = bms_add_member(*rels_used,
    1067       18200 :                                         ((Scan *) plan)->scanrelid);
    1068       18200 :             break;
    1069         598 :         case T_ForeignScan:
    1070        1196 :             *rels_used = bms_add_members(*rels_used,
    1071         598 :                                          ((ForeignScan *) plan)->fs_relids);
    1072         598 :             break;
    1073           0 :         case T_CustomScan:
    1074           0 :             *rels_used = bms_add_members(*rels_used,
    1075           0 :                                          ((CustomScan *) plan)->custom_relids);
    1076           0 :             break;
    1077         464 :         case T_ModifyTable:
    1078         928 :             *rels_used = bms_add_member(*rels_used,
    1079         464 :                                         ((ModifyTable *) plan)->nominalRelation);
    1080         464 :             if (((ModifyTable *) plan)->exclRelRTI)
    1081          52 :                 *rels_used = bms_add_member(*rels_used,
    1082          52 :                                             ((ModifyTable *) plan)->exclRelRTI);
    1083         464 :             break;
    1084        1892 :         case T_Append:
    1085        3784 :             *rels_used = bms_add_members(*rels_used,
    1086        1892 :                                          ((Append *) plan)->apprelids);
    1087        1892 :             break;
    1088         148 :         case T_MergeAppend:
    1089         296 :             *rels_used = bms_add_members(*rels_used,
    1090         148 :                                          ((MergeAppend *) plan)->apprelids);
    1091         148 :             break;
    1092       16870 :         default:
    1093       16870 :             break;
    1094             :     }
    1095             : 
    1096       38172 :     return planstate_tree_walker(planstate, ExplainPreScanNode, rels_used);
    1097             : }
    1098             : 
    1099             : /*
    1100             :  * ExplainNode -
    1101             :  *    Appends a description of a plan tree to es->str
    1102             :  *
    1103             :  * planstate points to the executor state node for the current plan node.
    1104             :  * We need to work from a PlanState node, not just a Plan node, in order to
    1105             :  * get at the instrumentation data (if any) as well as the list of subplans.
    1106             :  *
    1107             :  * ancestors is a list of parent Plan and SubPlan nodes, most-closely-nested
    1108             :  * first.  These are needed in order to interpret PARAM_EXEC Params.
    1109             :  *
    1110             :  * relationship describes the relationship of this plan node to its parent
    1111             :  * (eg, "Outer", "Inner"); it can be null at top level.  plan_name is an
    1112             :  * optional name to be attached to the node.
    1113             :  *
    1114             :  * In text format, es->indent is controlled in this function since we only
    1115             :  * want it to change at plan-node boundaries (but a few subroutines will
    1116             :  * transiently increment it).  In non-text formats, es->indent corresponds
    1117             :  * to the nesting depth of logical output groups, and therefore is controlled
    1118             :  * by ExplainOpenGroup/ExplainCloseGroup.
    1119             :  */
    1120             : static void
    1121       38004 : ExplainNode(PlanState *planstate, List *ancestors,
    1122             :             const char *relationship, const char *plan_name,
    1123             :             ExplainState *es)
    1124             : {
    1125       38004 :     Plan       *plan = planstate->plan;
    1126             :     const char *pname;          /* node type name for text output */
    1127             :     const char *sname;          /* node type name for non-text output */
    1128       38004 :     const char *strategy = NULL;
    1129       38004 :     const char *partialmode = NULL;
    1130       38004 :     const char *operation = NULL;
    1131       38004 :     const char *custom_name = NULL;
    1132       38004 :     ExplainWorkersState *save_workers_state = es->workers_state;
    1133       38004 :     int         save_indent = es->indent;
    1134             :     bool        haschildren;
    1135             : 
    1136             :     /*
    1137             :      * Prepare per-worker output buffers, if needed.  We'll append the data in
    1138             :      * these to the main output string further down.
    1139             :      */
    1140       38004 :     if (planstate->worker_instrument && es->analyze && !es->hide_workers)
    1141         640 :         es->workers_state = ExplainCreateWorkersState(planstate->worker_instrument->num_workers);
    1142             :     else
    1143       37364 :         es->workers_state = NULL;
    1144             : 
    1145             :     /* Identify plan node type, and print generic details */
    1146       38004 :     switch (nodeTag(plan))
    1147             :     {
    1148        1102 :         case T_Result:
    1149        1102 :             pname = sname = "Result";
    1150        1102 :             break;
    1151          88 :         case T_ProjectSet:
    1152          88 :             pname = sname = "ProjectSet";
    1153          88 :             break;
    1154         464 :         case T_ModifyTable:
    1155         464 :             sname = "ModifyTable";
    1156         464 :             switch (((ModifyTable *) plan)->operation)
    1157             :             {
    1158         124 :                 case CMD_INSERT:
    1159         124 :                     pname = operation = "Insert";
    1160         124 :                     break;
    1161         232 :                 case CMD_UPDATE:
    1162         232 :                     pname = operation = "Update";
    1163         232 :                     break;
    1164         108 :                 case CMD_DELETE:
    1165         108 :                     pname = operation = "Delete";
    1166         108 :                     break;
    1167           0 :                 default:
    1168           0 :                     pname = "???";
    1169           0 :                     break;
    1170             :             }
    1171         464 :             break;
    1172        1844 :         case T_Append:
    1173        1844 :             pname = sname = "Append";
    1174        1844 :             break;
    1175         148 :         case T_MergeAppend:
    1176         148 :             pname = sname = "Merge Append";
    1177         148 :             break;
    1178          12 :         case T_RecursiveUnion:
    1179          12 :             pname = sname = "Recursive Union";
    1180          12 :             break;
    1181           4 :         case T_BitmapAnd:
    1182           4 :             pname = sname = "BitmapAnd";
    1183           4 :             break;
    1184          68 :         case T_BitmapOr:
    1185          68 :             pname = sname = "BitmapOr";
    1186          68 :             break;
    1187        1246 :         case T_NestLoop:
    1188        1246 :             pname = sname = "Nested Loop";
    1189        1246 :             break;
    1190         392 :         case T_MergeJoin:
    1191         392 :             pname = "Merge";  /* "Join" gets added by jointype switch */
    1192         392 :             sname = "Merge Join";
    1193         392 :             break;
    1194        1948 :         case T_HashJoin:
    1195        1948 :             pname = "Hash";       /* "Join" gets added by jointype switch */
    1196        1948 :             sname = "Hash Join";
    1197        1948 :             break;
    1198       12308 :         case T_SeqScan:
    1199       12308 :             pname = sname = "Seq Scan";
    1200       12308 :             break;
    1201          48 :         case T_SampleScan:
    1202          48 :             pname = sname = "Sample Scan";
    1203          48 :             break;
    1204         336 :         case T_Gather:
    1205         336 :             pname = sname = "Gather";
    1206         336 :             break;
    1207          84 :         case T_GatherMerge:
    1208          84 :             pname = sname = "Gather Merge";
    1209          84 :             break;
    1210        2062 :         case T_IndexScan:
    1211        2062 :             pname = sname = "Index Scan";
    1212        2062 :             break;
    1213        1158 :         case T_IndexOnlyScan:
    1214        1158 :             pname = sname = "Index Only Scan";
    1215        1158 :             break;
    1216        1848 :         case T_BitmapIndexScan:
    1217        1848 :             pname = sname = "Bitmap Index Scan";
    1218        1848 :             break;
    1219        1752 :         case T_BitmapHeapScan:
    1220        1752 :             pname = sname = "Bitmap Heap Scan";
    1221        1752 :             break;
    1222          40 :         case T_TidScan:
    1223          40 :             pname = sname = "Tid Scan";
    1224          40 :             break;
    1225         236 :         case T_SubqueryScan:
    1226         236 :             pname = sname = "Subquery Scan";
    1227         236 :             break;
    1228         188 :         case T_FunctionScan:
    1229         188 :             pname = sname = "Function Scan";
    1230         188 :             break;
    1231          20 :         case T_TableFuncScan:
    1232          20 :             pname = sname = "Table Function Scan";
    1233          20 :             break;
    1234         154 :         case T_ValuesScan:
    1235         154 :             pname = sname = "Values Scan";
    1236         154 :             break;
    1237         110 :         case T_CteScan:
    1238         110 :             pname = sname = "CTE Scan";
    1239         110 :             break;
    1240          16 :         case T_NamedTuplestoreScan:
    1241          16 :             pname = sname = "Named Tuplestore Scan";
    1242          16 :             break;
    1243          12 :         case T_WorkTableScan:
    1244          12 :             pname = sname = "WorkTable Scan";
    1245          12 :             break;
    1246         598 :         case T_ForeignScan:
    1247         598 :             sname = "Foreign Scan";
    1248         598 :             switch (((ForeignScan *) plan)->operation)
    1249             :             {
    1250         542 :                 case CMD_SELECT:
    1251         542 :                     pname = "Foreign Scan";
    1252         542 :                     operation = "Select";
    1253         542 :                     break;
    1254           0 :                 case CMD_INSERT:
    1255           0 :                     pname = "Foreign Insert";
    1256           0 :                     operation = "Insert";
    1257           0 :                     break;
    1258          32 :                 case CMD_UPDATE:
    1259          32 :                     pname = "Foreign Update";
    1260          32 :                     operation = "Update";
    1261          32 :                     break;
    1262          24 :                 case CMD_DELETE:
    1263          24 :                     pname = "Foreign Delete";
    1264          24 :                     operation = "Delete";
    1265          24 :                     break;
    1266           0 :                 default:
    1267           0 :                     pname = "???";
    1268           0 :                     break;
    1269             :             }
    1270         598 :             break;
    1271           0 :         case T_CustomScan:
    1272           0 :             sname = "Custom Scan";
    1273           0 :             custom_name = ((CustomScan *) plan)->methods->CustomName;
    1274           0 :             if (custom_name)
    1275           0 :                 pname = psprintf("Custom Scan (%s)", custom_name);
    1276             :             else
    1277           0 :                 pname = sname;
    1278           0 :             break;
    1279         228 :         case T_Material:
    1280         228 :             pname = sname = "Materialize";
    1281         228 :             break;
    1282        2188 :         case T_Sort:
    1283        2188 :             pname = sname = "Sort";
    1284        2188 :             break;
    1285         124 :         case T_IncrementalSort:
    1286         124 :             pname = sname = "Incremental Sort";
    1287         124 :             break;
    1288          52 :         case T_Group:
    1289          52 :             pname = sname = "Group";
    1290          52 :             break;
    1291        4132 :         case T_Agg:
    1292             :             {
    1293        4132 :                 Agg        *agg = (Agg *) plan;
    1294             : 
    1295        4132 :                 sname = "Aggregate";
    1296        4132 :                 switch (agg->aggstrategy)
    1297             :                 {
    1298        3094 :                     case AGG_PLAIN:
    1299        3094 :                         pname = "Aggregate";
    1300        3094 :                         strategy = "Plain";
    1301        3094 :                         break;
    1302         246 :                     case AGG_SORTED:
    1303         246 :                         pname = "GroupAggregate";
    1304         246 :                         strategy = "Sorted";
    1305         246 :                         break;
    1306         744 :                     case AGG_HASHED:
    1307         744 :                         pname = "HashAggregate";
    1308         744 :                         strategy = "Hashed";
    1309         744 :                         break;
    1310          48 :                     case AGG_MIXED:
    1311          48 :                         pname = "MixedAggregate";
    1312          48 :                         strategy = "Mixed";
    1313          48 :                         break;
    1314           0 :                     default:
    1315           0 :                         pname = "Aggregate ???";
    1316           0 :                         strategy = "???";
    1317           0 :                         break;
    1318             :                 }
    1319             : 
    1320        4132 :                 if (DO_AGGSPLIT_SKIPFINAL(agg->aggsplit))
    1321             :                 {
    1322         402 :                     partialmode = "Partial";
    1323         402 :                     pname = psprintf("%s %s", partialmode, pname);
    1324             :                 }
    1325        3730 :                 else if (DO_AGGSPLIT_COMBINE(agg->aggsplit))
    1326             :                 {
    1327         286 :                     partialmode = "Finalize";
    1328         286 :                     pname = psprintf("%s %s", partialmode, pname);
    1329             :                 }
    1330             :                 else
    1331        3444 :                     partialmode = "Simple";
    1332             :             }
    1333        4132 :             break;
    1334          98 :         case T_WindowAgg:
    1335          98 :             pname = sname = "WindowAgg";
    1336          98 :             break;
    1337          62 :         case T_Unique:
    1338          62 :             pname = sname = "Unique";
    1339          62 :             break;
    1340          28 :         case T_SetOp:
    1341          28 :             sname = "SetOp";
    1342          28 :             switch (((SetOp *) plan)->strategy)
    1343             :             {
    1344          12 :                 case SETOP_SORTED:
    1345          12 :                     pname = "SetOp";
    1346          12 :                     strategy = "Sorted";
    1347          12 :                     break;
    1348          16 :                 case SETOP_HASHED:
    1349          16 :                     pname = "HashSetOp";
    1350          16 :                     strategy = "Hashed";
    1351          16 :                     break;
    1352           0 :                 default:
    1353           0 :                     pname = "SetOp ???";
    1354           0 :                     strategy = "???";
    1355           0 :                     break;
    1356             :             }
    1357          28 :             break;
    1358         330 :         case T_LockRows:
    1359         330 :             pname = sname = "LockRows";
    1360         330 :             break;
    1361         528 :         case T_Limit:
    1362         528 :             pname = sname = "Limit";
    1363         528 :             break;
    1364        1948 :         case T_Hash:
    1365        1948 :             pname = sname = "Hash";
    1366        1948 :             break;
    1367           0 :         default:
    1368           0 :             pname = sname = "???";
    1369           0 :             break;
    1370             :     }
    1371             : 
    1372       38004 :     ExplainOpenGroup("Plan",
    1373             :                      relationship ? NULL : "Plan",
    1374             :                      true, es);
    1375             : 
    1376       38004 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    1377             :     {
    1378       37340 :         if (plan_name)
    1379             :         {
    1380         838 :             ExplainIndentText(es);
    1381         838 :             appendStringInfo(es->str, "%s\n", plan_name);
    1382         838 :             es->indent++;
    1383             :         }
    1384       37340 :         if (es->indent)
    1385             :         {
    1386       27114 :             ExplainIndentText(es);
    1387       27114 :             appendStringInfoString(es->str, "->  ");
    1388       27114 :             es->indent += 2;
    1389             :         }
    1390       37340 :         if (plan->parallel_aware)
    1391         640 :             appendStringInfoString(es->str, "Parallel ");
    1392       37340 :         appendStringInfoString(es->str, pname);
    1393       37340 :         es->indent++;
    1394             :     }
    1395             :     else
    1396             :     {
    1397         664 :         ExplainPropertyText("Node Type", sname, es);
    1398         664 :         if (strategy)
    1399         108 :             ExplainPropertyText("Strategy", strategy, es);
    1400         664 :         if (partialmode)
    1401         108 :             ExplainPropertyText("Partial Mode", partialmode, es);
    1402         664 :         if (operation)
    1403           4 :             ExplainPropertyText("Operation", operation, es);
    1404         664 :         if (relationship)
    1405         512 :             ExplainPropertyText("Parent Relationship", relationship, es);
    1406         664 :         if (plan_name)
    1407           0 :             ExplainPropertyText("Subplan Name", plan_name, es);
    1408         664 :         if (custom_name)
    1409           0 :             ExplainPropertyText("Custom Plan Provider", custom_name, es);
    1410         664 :         ExplainPropertyBool("Parallel Aware", plan->parallel_aware, es);
    1411             :     }
    1412             : 
    1413       38004 :     switch (nodeTag(plan))
    1414             :     {
    1415       14868 :         case T_SeqScan:
    1416             :         case T_SampleScan:
    1417             :         case T_BitmapHeapScan:
    1418             :         case T_TidScan:
    1419             :         case T_SubqueryScan:
    1420             :         case T_FunctionScan:
    1421             :         case T_TableFuncScan:
    1422             :         case T_ValuesScan:
    1423             :         case T_CteScan:
    1424             :         case T_WorkTableScan:
    1425       14868 :             ExplainScanTarget((Scan *) plan, es);
    1426       14868 :             break;
    1427         598 :         case T_ForeignScan:
    1428             :         case T_CustomScan:
    1429         598 :             if (((Scan *) plan)->scanrelid > 0)
    1430         392 :                 ExplainScanTarget((Scan *) plan, es);
    1431         598 :             break;
    1432        2062 :         case T_IndexScan:
    1433             :             {
    1434        2062 :                 IndexScan  *indexscan = (IndexScan *) plan;
    1435             : 
    1436        2062 :                 ExplainIndexScanDetails(indexscan->indexid,
    1437             :                                         indexscan->indexorderdir,
    1438             :                                         es);
    1439        2062 :                 ExplainScanTarget((Scan *) indexscan, es);
    1440             :             }
    1441        2062 :             break;
    1442        1158 :         case T_IndexOnlyScan:
    1443             :             {
    1444        1158 :                 IndexOnlyScan *indexonlyscan = (IndexOnlyScan *) plan;
    1445             : 
    1446        1158 :                 ExplainIndexScanDetails(indexonlyscan->indexid,
    1447             :                                         indexonlyscan->indexorderdir,
    1448             :                                         es);
    1449        1158 :                 ExplainScanTarget((Scan *) indexonlyscan, es);
    1450             :             }
    1451        1158 :             break;
    1452        1848 :         case T_BitmapIndexScan:
    1453             :             {
    1454        1848 :                 BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
    1455             :                 const char *indexname =
    1456        1848 :                 explain_get_index_name(bitmapindexscan->indexid);
    1457             : 
    1458        1848 :                 if (es->format == EXPLAIN_FORMAT_TEXT)
    1459        1808 :                     appendStringInfo(es->str, " on %s", indexname);
    1460             :                 else
    1461          40 :                     ExplainPropertyText("Index Name", indexname, es);
    1462             :             }
    1463        1848 :             break;
    1464         464 :         case T_ModifyTable:
    1465         464 :             ExplainModifyTarget((ModifyTable *) plan, es);
    1466         464 :             break;
    1467        3586 :         case T_NestLoop:
    1468             :         case T_MergeJoin:
    1469             :         case T_HashJoin:
    1470             :             {
    1471             :                 const char *jointype;
    1472             : 
    1473        3586 :                 switch (((Join *) plan)->jointype)
    1474             :                 {
    1475        2058 :                     case JOIN_INNER:
    1476        2058 :                         jointype = "Inner";
    1477        2058 :                         break;
    1478         576 :                     case JOIN_LEFT:
    1479         576 :                         jointype = "Left";
    1480         576 :                         break;
    1481         342 :                     case JOIN_FULL:
    1482         342 :                         jointype = "Full";
    1483         342 :                         break;
    1484         350 :                     case JOIN_RIGHT:
    1485         350 :                         jointype = "Right";
    1486         350 :                         break;
    1487         162 :                     case JOIN_SEMI:
    1488         162 :                         jointype = "Semi";
    1489         162 :                         break;
    1490          98 :                     case JOIN_ANTI:
    1491          98 :                         jointype = "Anti";
    1492          98 :                         break;
    1493           0 :                     default:
    1494           0 :                         jointype = "???";
    1495           0 :                         break;
    1496             :                 }
    1497        3586 :                 if (es->format == EXPLAIN_FORMAT_TEXT)
    1498             :                 {
    1499             :                     /*
    1500             :                      * For historical reasons, the join type is interpolated
    1501             :                      * into the node type name...
    1502             :                      */
    1503        3498 :                     if (((Join *) plan)->jointype != JOIN_INNER)
    1504        1508 :                         appendStringInfo(es->str, " %s Join", jointype);
    1505        1990 :                     else if (!IsA(plan, NestLoop))
    1506        1142 :                         appendStringInfoString(es->str, " Join");
    1507             :                 }
    1508             :                 else
    1509          88 :                     ExplainPropertyText("Join Type", jointype, es);
    1510             :             }
    1511        3586 :             break;
    1512          28 :         case T_SetOp:
    1513             :             {
    1514             :                 const char *setopcmd;
    1515             : 
    1516          28 :                 switch (((SetOp *) plan)->cmd)
    1517             :                 {
    1518          16 :                     case SETOPCMD_INTERSECT:
    1519          16 :                         setopcmd = "Intersect";
    1520          16 :                         break;
    1521           0 :                     case SETOPCMD_INTERSECT_ALL:
    1522           0 :                         setopcmd = "Intersect All";
    1523           0 :                         break;
    1524          12 :                     case SETOPCMD_EXCEPT:
    1525          12 :                         setopcmd = "Except";
    1526          12 :                         break;
    1527           0 :                     case SETOPCMD_EXCEPT_ALL:
    1528           0 :                         setopcmd = "Except All";
    1529           0 :                         break;
    1530           0 :                     default:
    1531           0 :                         setopcmd = "???";
    1532           0 :                         break;
    1533             :                 }
    1534          28 :                 if (es->format == EXPLAIN_FORMAT_TEXT)
    1535          28 :                     appendStringInfo(es->str, " %s", setopcmd);
    1536             :                 else
    1537           0 :                     ExplainPropertyText("Command", setopcmd, es);
    1538             :             }
    1539          28 :             break;
    1540       13392 :         default:
    1541       13392 :             break;
    1542             :     }
    1543             : 
    1544       38004 :     if (es->costs)
    1545             :     {
    1546       11256 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    1547             :         {
    1548       10664 :             appendStringInfo(es->str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
    1549             :                              plan->startup_cost, plan->total_cost,
    1550             :                              plan->plan_rows, plan->plan_width);
    1551             :         }
    1552             :         else
    1553             :         {
    1554         592 :             ExplainPropertyFloat("Startup Cost", NULL, plan->startup_cost,
    1555             :                                  2, es);
    1556         592 :             ExplainPropertyFloat("Total Cost", NULL, plan->total_cost,
    1557             :                                  2, es);
    1558         592 :             ExplainPropertyFloat("Plan Rows", NULL, plan->plan_rows,
    1559             :                                  0, es);
    1560         592 :             ExplainPropertyInteger("Plan Width", NULL, plan->plan_width,
    1561             :                                    es);
    1562             :         }
    1563             :     }
    1564             : 
    1565             :     /*
    1566             :      * We have to forcibly clean up the instrumentation state because we
    1567             :      * haven't done ExecutorEnd yet.  This is pretty grotty ...
    1568             :      *
    1569             :      * Note: contrib/auto_explain could cause instrumentation to be set up
    1570             :      * even though we didn't ask for it here.  Be careful not to print any
    1571             :      * instrumentation results the user didn't ask for.  But we do the
    1572             :      * InstrEndLoop call anyway, if possible, to reduce the number of cases
    1573             :      * auto_explain has to contend with.
    1574             :      */
    1575       38004 :     if (planstate->instrument)
    1576        3392 :         InstrEndLoop(planstate->instrument);
    1577             : 
    1578       38004 :     if (es->analyze &&
    1579        3392 :         planstate->instrument && planstate->instrument->nloops > 0)
    1580        2932 :     {
    1581        2932 :         double      nloops = planstate->instrument->nloops;
    1582        2932 :         double      startup_ms = 1000.0 * planstate->instrument->startup / nloops;
    1583        2932 :         double      total_ms = 1000.0 * planstate->instrument->total / nloops;
    1584        2932 :         double      rows = planstate->instrument->ntuples / nloops;
    1585             : 
    1586        2932 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    1587             :         {
    1588        2280 :             if (es->timing)
    1589        1032 :                 appendStringInfo(es->str,
    1590             :                                  " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
    1591             :                                  startup_ms, total_ms, rows, nloops);
    1592             :             else
    1593        1248 :                 appendStringInfo(es->str,
    1594             :                                  " (actual rows=%.0f loops=%.0f)",
    1595             :                                  rows, nloops);
    1596             :         }
    1597             :         else
    1598             :         {
    1599         652 :             if (es->timing)
    1600             :             {
    1601         588 :                 ExplainPropertyFloat("Actual Startup Time", "s", startup_ms,
    1602             :                                      3, es);
    1603         588 :                 ExplainPropertyFloat("Actual Total Time", "s", total_ms,
    1604             :                                      3, es);
    1605             :             }
    1606         652 :             ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es);
    1607         652 :             ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es);
    1608             :         }
    1609             :     }
    1610       35072 :     else if (es->analyze)
    1611             :     {
    1612         460 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    1613         460 :             appendStringInfoString(es->str, " (never executed)");
    1614             :         else
    1615             :         {
    1616           0 :             if (es->timing)
    1617             :             {
    1618           0 :                 ExplainPropertyFloat("Actual Startup Time", "ms", 0.0, 3, es);
    1619           0 :                 ExplainPropertyFloat("Actual Total Time", "ms", 0.0, 3, es);
    1620             :             }
    1621           0 :             ExplainPropertyFloat("Actual Rows", NULL, 0.0, 0, es);
    1622           0 :             ExplainPropertyFloat("Actual Loops", NULL, 0.0, 0, es);
    1623             :         }
    1624             :     }
    1625             : 
    1626             :     /* in text format, first line ends here */
    1627       38004 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    1628       37340 :         appendStringInfoChar(es->str, '\n');
    1629             : 
    1630             :     /* prepare per-worker general execution details */
    1631       38004 :     if (es->workers_state && es->verbose)
    1632             :     {
    1633           8 :         WorkerInstrumentation *w = planstate->worker_instrument;
    1634             : 
    1635          40 :         for (int n = 0; n < w->num_workers; n++)
    1636             :         {
    1637          32 :             Instrumentation *instrument = &w->instrument[n];
    1638          32 :             double      nloops = instrument->nloops;
    1639             :             double      startup_ms;
    1640             :             double      total_ms;
    1641             :             double      rows;
    1642             : 
    1643          32 :             if (nloops <= 0)
    1644           0 :                 continue;
    1645          32 :             startup_ms = 1000.0 * instrument->startup / nloops;
    1646          32 :             total_ms = 1000.0 * instrument->total / nloops;
    1647          32 :             rows = instrument->ntuples / nloops;
    1648             : 
    1649          32 :             ExplainOpenWorker(n, es);
    1650             : 
    1651          32 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    1652             :             {
    1653           0 :                 ExplainIndentText(es);
    1654           0 :                 if (es->timing)
    1655           0 :                     appendStringInfo(es->str,
    1656             :                                      "actual time=%.3f..%.3f rows=%.0f loops=%.0f\n",
    1657             :                                      startup_ms, total_ms, rows, nloops);
    1658             :                 else
    1659           0 :                     appendStringInfo(es->str,
    1660             :                                      "actual rows=%.0f loops=%.0f\n",
    1661             :                                      rows, nloops);
    1662             :             }
    1663             :             else
    1664             :             {
    1665          32 :                 if (es->timing)
    1666             :                 {
    1667          32 :                     ExplainPropertyFloat("Actual Startup Time", "ms",
    1668             :                                          startup_ms, 3, es);
    1669          32 :                     ExplainPropertyFloat("Actual Total Time", "ms",
    1670             :                                          total_ms, 3, es);
    1671             :                 }
    1672          32 :                 ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es);
    1673          32 :                 ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es);
    1674             :             }
    1675             : 
    1676          32 :             ExplainCloseWorker(n, es);
    1677             :         }
    1678             :     }
    1679             : 
    1680             :     /* target list */
    1681       38004 :     if (es->verbose)
    1682        3660 :         show_plan_tlist(planstate, ancestors, es);
    1683             : 
    1684             :     /* unique join */
    1685       38004 :     switch (nodeTag(plan))
    1686             :     {
    1687        3586 :         case T_NestLoop:
    1688             :         case T_MergeJoin:
    1689             :         case T_HashJoin:
    1690             :             /* try not to be too chatty about this in text mode */
    1691        3586 :             if (es->format != EXPLAIN_FORMAT_TEXT ||
    1692        3498 :                 (es->verbose && ((Join *) plan)->inner_unique))
    1693         150 :                 ExplainPropertyBool("Inner Unique",
    1694         150 :                                     ((Join *) plan)->inner_unique,
    1695             :                                     es);
    1696        3586 :             break;
    1697       34418 :         default:
    1698       34418 :             break;
    1699             :     }
    1700             : 
    1701             :     /* quals, sort keys, etc */
    1702       38004 :     switch (nodeTag(plan))
    1703             :     {
    1704        2062 :         case T_IndexScan:
    1705        2062 :             show_scan_qual(((IndexScan *) plan)->indexqualorig,
    1706             :                            "Index Cond", planstate, ancestors, es);
    1707        2062 :             if (((IndexScan *) plan)->indexqualorig)
    1708        1498 :                 show_instrumentation_count("Rows Removed by Index Recheck", 2,
    1709             :                                            planstate, es);
    1710        2062 :             show_scan_qual(((IndexScan *) plan)->indexorderbyorig,
    1711             :                            "Order By", planstate, ancestors, es);
    1712        2062 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1713        2062 :             if (plan->qual)
    1714         262 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1715             :                                            planstate, es);
    1716        2062 :             break;
    1717        1158 :         case T_IndexOnlyScan:
    1718        1158 :             show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
    1719             :                            "Index Cond", planstate, ancestors, es);
    1720        1158 :             if (((IndexOnlyScan *) plan)->indexqual)
    1721         784 :                 show_instrumentation_count("Rows Removed by Index Recheck", 2,
    1722             :                                            planstate, es);
    1723        1158 :             show_scan_qual(((IndexOnlyScan *) plan)->indexorderby,
    1724             :                            "Order By", planstate, ancestors, es);
    1725        1158 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1726        1158 :             if (plan->qual)
    1727          40 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1728             :                                            planstate, es);
    1729        1158 :             if (es->analyze)
    1730           0 :                 ExplainPropertyFloat("Heap Fetches", NULL,
    1731           0 :                                      planstate->instrument->ntuples2, 0, es);
    1732        1158 :             break;
    1733        1848 :         case T_BitmapIndexScan:
    1734        1848 :             show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
    1735             :                            "Index Cond", planstate, ancestors, es);
    1736        1848 :             break;
    1737        1752 :         case T_BitmapHeapScan:
    1738        1752 :             show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
    1739             :                            "Recheck Cond", planstate, ancestors, es);
    1740        1752 :             if (((BitmapHeapScan *) plan)->bitmapqualorig)
    1741        1716 :                 show_instrumentation_count("Rows Removed by Index Recheck", 2,
    1742             :                                            planstate, es);
    1743        1752 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1744        1752 :             if (plan->qual)
    1745         184 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1746             :                                            planstate, es);
    1747        1752 :             if (es->analyze)
    1748         316 :                 show_tidbitmap_info((BitmapHeapScanState *) planstate, es);
    1749        1752 :             break;
    1750          48 :         case T_SampleScan:
    1751          48 :             show_tablesample(((SampleScan *) plan)->tablesample,
    1752             :                              planstate, ancestors, es);
    1753             :             /* fall through to print additional fields the same as SeqScan */
    1754             :             /* FALLTHROUGH */
    1755       12884 :         case T_SeqScan:
    1756             :         case T_ValuesScan:
    1757             :         case T_CteScan:
    1758             :         case T_NamedTuplestoreScan:
    1759             :         case T_WorkTableScan:
    1760             :         case T_SubqueryScan:
    1761       12884 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1762       12884 :             if (plan->qual)
    1763        6954 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1764             :                                            planstate, es);
    1765       12884 :             break;
    1766         336 :         case T_Gather:
    1767         276 :             {
    1768         336 :                 Gather     *gather = (Gather *) plan;
    1769             : 
    1770         336 :                 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1771         336 :                 if (plan->qual)
    1772           0 :                     show_instrumentation_count("Rows Removed by Filter", 1,
    1773             :                                                planstate, es);
    1774         336 :                 ExplainPropertyInteger("Workers Planned", NULL,
    1775         336 :                                        gather->num_workers, es);
    1776             : 
    1777             :                 /* Show params evaluated at gather node */
    1778         336 :                 if (gather->initParam)
    1779          16 :                     show_eval_params(gather->initParam, es);
    1780             : 
    1781         336 :                 if (es->analyze)
    1782             :                 {
    1783             :                     int         nworkers;
    1784             : 
    1785         104 :                     nworkers = ((GatherState *) planstate)->nworkers_launched;
    1786         104 :                     ExplainPropertyInteger("Workers Launched", NULL,
    1787             :                                            nworkers, es);
    1788             :                 }
    1789             : 
    1790         336 :                 if (gather->single_copy || es->format != EXPLAIN_FORMAT_TEXT)
    1791          60 :                     ExplainPropertyBool("Single Copy", gather->single_copy, es);
    1792             :             }
    1793         336 :             break;
    1794          84 :         case T_GatherMerge:
    1795          76 :             {
    1796          84 :                 GatherMerge *gm = (GatherMerge *) plan;
    1797             : 
    1798          84 :                 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1799          84 :                 if (plan->qual)
    1800           0 :                     show_instrumentation_count("Rows Removed by Filter", 1,
    1801             :                                                planstate, es);
    1802          84 :                 ExplainPropertyInteger("Workers Planned", NULL,
    1803          84 :                                        gm->num_workers, es);
    1804             : 
    1805             :                 /* Show params evaluated at gather-merge node */
    1806          84 :                 if (gm->initParam)
    1807           0 :                     show_eval_params(gm->initParam, es);
    1808             : 
    1809          84 :                 if (es->analyze)
    1810             :                 {
    1811             :                     int         nworkers;
    1812             : 
    1813           8 :                     nworkers = ((GatherMergeState *) planstate)->nworkers_launched;
    1814           8 :                     ExplainPropertyInteger("Workers Launched", NULL,
    1815             :                                            nworkers, es);
    1816             :                 }
    1817             :             }
    1818          84 :             break;
    1819         188 :         case T_FunctionScan:
    1820         188 :             if (es->verbose)
    1821             :             {
    1822          56 :                 List       *fexprs = NIL;
    1823             :                 ListCell   *lc;
    1824             : 
    1825         112 :                 foreach(lc, ((FunctionScan *) plan)->functions)
    1826             :                 {
    1827          56 :                     RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
    1828             : 
    1829          56 :                     fexprs = lappend(fexprs, rtfunc->funcexpr);
    1830             :                 }
    1831             :                 /* We rely on show_expression to insert commas as needed */
    1832          56 :                 show_expression((Node *) fexprs,
    1833             :                                 "Function Call", planstate, ancestors,
    1834          56 :                                 es->verbose, es);
    1835             :             }
    1836         188 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1837         188 :             if (plan->qual)
    1838          12 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1839             :                                            planstate, es);
    1840         188 :             break;
    1841          20 :         case T_TableFuncScan:
    1842          20 :             if (es->verbose)
    1843             :             {
    1844          16 :                 TableFunc  *tablefunc = ((TableFuncScan *) plan)->tablefunc;
    1845             : 
    1846          16 :                 show_expression((Node *) tablefunc,
    1847             :                                 "Table Function Call", planstate, ancestors,
    1848          16 :                                 es->verbose, es);
    1849             :             }
    1850          20 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1851          20 :             if (plan->qual)
    1852           8 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1853             :                                            planstate, es);
    1854          20 :             break;
    1855          40 :         case T_TidScan:
    1856          32 :             {
    1857             :                 /*
    1858             :                  * The tidquals list has OR semantics, so be sure to show it
    1859             :                  * as an OR condition.
    1860             :                  */
    1861          40 :                 List       *tidquals = ((TidScan *) plan)->tidquals;
    1862             : 
    1863          40 :                 if (list_length(tidquals) > 1)
    1864           8 :                     tidquals = list_make1(make_orclause(tidquals));
    1865          40 :                 show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
    1866          40 :                 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1867          40 :                 if (plan->qual)
    1868           8 :                     show_instrumentation_count("Rows Removed by Filter", 1,
    1869             :                                                planstate, es);
    1870             :             }
    1871          40 :             break;
    1872         598 :         case T_ForeignScan:
    1873         598 :             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1874         598 :             if (plan->qual)
    1875          56 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1876             :                                            planstate, es);
    1877         598 :             show_foreignscan_info((ForeignScanState *) planstate, es);
    1878         598 :             break;
    1879           0 :         case T_CustomScan:
    1880           0 :             {
    1881           0 :                 CustomScanState *css = (CustomScanState *) planstate;
    1882             : 
    1883           0 :                 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
    1884           0 :                 if (plan->qual)
    1885           0 :                     show_instrumentation_count("Rows Removed by Filter", 1,
    1886             :                                                planstate, es);
    1887           0 :                 if (css->methods->ExplainCustomScan)
    1888           0 :                     css->methods->ExplainCustomScan(css, ancestors, es);
    1889             :             }
    1890           0 :             break;
    1891        1246 :         case T_NestLoop:
    1892        1246 :             show_upper_qual(((NestLoop *) plan)->join.joinqual,
    1893             :                             "Join Filter", planstate, ancestors, es);
    1894        1246 :             if (((NestLoop *) plan)->join.joinqual)
    1895         404 :                 show_instrumentation_count("Rows Removed by Join Filter", 1,
    1896             :                                            planstate, es);
    1897        1246 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1898        1246 :             if (plan->qual)
    1899          20 :                 show_instrumentation_count("Rows Removed by Filter", 2,
    1900             :                                            planstate, es);
    1901        1246 :             break;
    1902         392 :         case T_MergeJoin:
    1903         392 :             show_upper_qual(((MergeJoin *) plan)->mergeclauses,
    1904             :                             "Merge Cond", planstate, ancestors, es);
    1905         392 :             show_upper_qual(((MergeJoin *) plan)->join.joinqual,
    1906             :                             "Join Filter", planstate, ancestors, es);
    1907         392 :             if (((MergeJoin *) plan)->join.joinqual)
    1908          16 :                 show_instrumentation_count("Rows Removed by Join Filter", 1,
    1909             :                                            planstate, es);
    1910         392 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1911         392 :             if (plan->qual)
    1912          16 :                 show_instrumentation_count("Rows Removed by Filter", 2,
    1913             :                                            planstate, es);
    1914         392 :             break;
    1915        1948 :         case T_HashJoin:
    1916        1948 :             show_upper_qual(((HashJoin *) plan)->hashclauses,
    1917             :                             "Hash Cond", planstate, ancestors, es);
    1918        1948 :             show_upper_qual(((HashJoin *) plan)->join.joinqual,
    1919             :                             "Join Filter", planstate, ancestors, es);
    1920        1948 :             if (((HashJoin *) plan)->join.joinqual)
    1921          12 :                 show_instrumentation_count("Rows Removed by Join Filter", 1,
    1922             :                                            planstate, es);
    1923        1948 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1924        1948 :             if (plan->qual)
    1925         160 :                 show_instrumentation_count("Rows Removed by Filter", 2,
    1926             :                                            planstate, es);
    1927        1948 :             break;
    1928        4132 :         case T_Agg:
    1929        4132 :             show_agg_keys(castNode(AggState, planstate), ancestors, es);
    1930        4132 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1931        4132 :             show_hashagg_info((AggState *) planstate, es);
    1932        4132 :             if (plan->qual)
    1933         216 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1934             :                                            planstate, es);
    1935        4132 :             break;
    1936          52 :         case T_Group:
    1937          52 :             show_group_keys(castNode(GroupState, planstate), ancestors, es);
    1938          52 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1939          52 :             if (plan->qual)
    1940           0 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1941             :                                            planstate, es);
    1942          52 :             break;
    1943        2188 :         case T_Sort:
    1944        2188 :             show_sort_keys(castNode(SortState, planstate), ancestors, es);
    1945        2188 :             show_sort_info(castNode(SortState, planstate), es);
    1946        2188 :             break;
    1947         124 :         case T_IncrementalSort:
    1948         124 :             show_incremental_sort_keys(castNode(IncrementalSortState, planstate),
    1949             :                                        ancestors, es);
    1950         124 :             show_incremental_sort_info(castNode(IncrementalSortState, planstate),
    1951             :                                        es);
    1952         124 :             break;
    1953         148 :         case T_MergeAppend:
    1954         148 :             show_merge_append_keys(castNode(MergeAppendState, planstate),
    1955             :                                    ancestors, es);
    1956         148 :             break;
    1957        1102 :         case T_Result:
    1958        1102 :             show_upper_qual((List *) ((Result *) plan)->resconstantqual,
    1959             :                             "One-Time Filter", planstate, ancestors, es);
    1960        1102 :             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
    1961        1102 :             if (plan->qual)
    1962           0 :                 show_instrumentation_count("Rows Removed by Filter", 1,
    1963             :                                            planstate, es);
    1964        1102 :             break;
    1965         464 :         case T_ModifyTable:
    1966         464 :             show_modifytable_info(castNode(ModifyTableState, planstate), ancestors,
    1967             :                                   es);
    1968         464 :             break;
    1969        1948 :         case T_Hash:
    1970        1948 :             show_hash_info(castNode(HashState, planstate), es);
    1971        1948 :             break;
    1972        3290 :         default:
    1973        3290 :             break;
    1974             :     }
    1975             : 
    1976             :     /*
    1977             :      * Prepare per-worker JIT instrumentation.  As with the overall JIT
    1978             :      * summary, this is printed only if printing costs is enabled.
    1979             :      */
    1980       38004 :     if (es->workers_state && es->costs && es->verbose)
    1981             :     {
    1982           8 :         SharedJitInstrumentation *w = planstate->worker_jit_instrument;
    1983             : 
    1984           8 :         if (w)
    1985             :         {
    1986           0 :             for (int n = 0; n < w->num_workers; n++)
    1987             :             {
    1988           0 :                 ExplainOpenWorker(n, es);
    1989           0 :                 ExplainPrintJIT(es, planstate->state->es_jit_flags,
    1990             :                                 &w->jit_instr[n]);
    1991           0 :                 ExplainCloseWorker(n, es);
    1992             :             }
    1993             :         }
    1994             :     }
    1995             : 
    1996             :     /* Show buffer/WAL usage */
    1997       38004 :     if (es->buffers && planstate->instrument)
    1998          28 :         show_buffer_usage(es, &planstate->instrument->bufusage);
    1999       38004 :     if (es->wal && planstate->instrument)
    2000           0 :         show_wal_usage(es, &planstate->instrument->walusage);
    2001             : 
    2002             :     /* Prepare per-worker buffer/WAL usage */
    2003       38004 :     if (es->workers_state && (es->buffers || es->wal) && es->verbose)
    2004             :     {
    2005           8 :         WorkerInstrumentation *w = planstate->worker_instrument;
    2006             : 
    2007          40 :         for (int n = 0; n < w->num_workers; n++)
    2008             :         {
    2009          32 :             Instrumentation *instrument = &w->instrument[n];
    2010          32 :             double      nloops = instrument->nloops;
    2011             : 
    2012          32 :             if (nloops <= 0)
    2013           0 :                 continue;
    2014             : 
    2015          32 :             ExplainOpenWorker(n, es);
    2016          32 :             if (es->buffers)
    2017          32 :                 show_buffer_usage(es, &instrument->bufusage);
    2018          32 :             if (es->wal)
    2019           0 :                 show_wal_usage(es, &instrument->walusage);
    2020          32 :             ExplainCloseWorker(n, es);
    2021             :         }
    2022             :     }
    2023             : 
    2024             :     /* Show per-worker details for this plan node, then pop that stack */
    2025       38004 :     if (es->workers_state)
    2026         640 :         ExplainFlushWorkersState(es);
    2027       38004 :     es->workers_state = save_workers_state;
    2028             : 
    2029             :     /*
    2030             :      * If partition pruning was done during executor initialization, the
    2031             :      * number of child plans we'll display below will be less than the number
    2032             :      * of subplans that was specified in the plan.  To make this a bit less
    2033             :      * mysterious, emit an indication that this happened.  Note that this
    2034             :      * field is emitted now because we want it to be a property of the parent
    2035             :      * node; it *cannot* be emitted within the Plans sub-node we'll open next.
    2036             :      */
    2037       38004 :     switch (nodeTag(plan))
    2038             :     {
    2039        1844 :         case T_Append:
    2040        1844 :             ExplainMissingMembers(((AppendState *) planstate)->as_nplans,
    2041        1844 :                                   list_length(((Append *) plan)->appendplans),
    2042             :                                   es);
    2043        1844 :             break;
    2044         148 :         case T_MergeAppend:
    2045         148 :             ExplainMissingMembers(((MergeAppendState *) planstate)->ms_nplans,
    2046         148 :                                   list_length(((MergeAppend *) plan)->mergeplans),
    2047             :                                   es);
    2048         148 :             break;
    2049       36012 :         default:
    2050       36012 :             break;
    2051             :     }
    2052             : 
    2053             :     /* Get ready to display the child plans */
    2054      113410 :     haschildren = planstate->initPlan ||
    2055       37402 :         outerPlanState(planstate) ||
    2056       21902 :         innerPlanState(planstate) ||
    2057       21902 :         IsA(plan, ModifyTable) ||
    2058       21458 :         IsA(plan, Append) ||
    2059       19678 :         IsA(plan, MergeAppend) ||
    2060       19534 :         IsA(plan, BitmapAnd) ||
    2061       19530 :         IsA(plan, BitmapOr) ||
    2062       19462 :         IsA(plan, SubqueryScan) ||
    2063       19226 :         (IsA(planstate, CustomScanState) &&
    2064       75406 :          ((CustomScanState *) planstate)->custom_ps != NIL) ||
    2065       19226 :         planstate->subPlan;
    2066       38004 :     if (haschildren)
    2067             :     {
    2068       18904 :         ExplainOpenGroup("Plans", "Plans", false, es);
    2069             :         /* Pass current Plan as head of ancestors list for children */
    2070       18904 :         ancestors = lcons(plan, ancestors);
    2071             :     }
    2072             : 
    2073             :     /* initPlan-s */
    2074       38004 :     if (planstate->initPlan)
    2075         602 :         ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es);
    2076             : 
    2077             :     /* lefttree */
    2078       38004 :     if (outerPlanState(planstate))
    2079       15660 :         ExplainNode(outerPlanState(planstate), ancestors,
    2080             :                     "Outer", NULL, es);
    2081             : 
    2082             :     /* righttree */
    2083       38004 :     if (innerPlanState(planstate))
    2084        3598 :         ExplainNode(innerPlanState(planstate), ancestors,
    2085             :                     "Inner", NULL, es);
    2086             : 
    2087             :     /* special child plans */
    2088       38004 :     switch (nodeTag(plan))
    2089             :     {
    2090         464 :         case T_ModifyTable:
    2091         464 :             ExplainMemberNodes(((ModifyTableState *) planstate)->mt_plans,
    2092             :                                ((ModifyTableState *) planstate)->mt_nplans,
    2093             :                                ancestors, es);
    2094         464 :             break;
    2095        1844 :         case T_Append:
    2096        1844 :             ExplainMemberNodes(((AppendState *) planstate)->appendplans,
    2097             :                                ((AppendState *) planstate)->as_nplans,
    2098             :                                ancestors, es);
    2099        1844 :             break;
    2100         148 :         case T_MergeAppend:
    2101         148 :             ExplainMemberNodes(((MergeAppendState *) planstate)->mergeplans,
    2102             :                                ((MergeAppendState *) planstate)->ms_nplans,
    2103             :                                ancestors, es);
    2104         148 :             break;
    2105           4 :         case T_BitmapAnd:
    2106           4 :             ExplainMemberNodes(((BitmapAndState *) planstate)->bitmapplans,
    2107             :                                ((BitmapAndState *) planstate)->nplans,
    2108             :                                ancestors, es);
    2109           4 :             break;
    2110          68 :         case T_BitmapOr:
    2111          68 :             ExplainMemberNodes(((BitmapOrState *) planstate)->bitmapplans,
    2112             :                                ((BitmapOrState *) planstate)->nplans,
    2113             :                                ancestors, es);
    2114          68 :             break;
    2115         236 :         case T_SubqueryScan:
    2116         236 :             ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,
    2117             :                         "Subquery", NULL, es);
    2118         236 :             break;
    2119           0 :         case T_CustomScan:
    2120           0 :             ExplainCustomChildren((CustomScanState *) planstate,
    2121             :                                   ancestors, es);
    2122           0 :             break;
    2123       35240 :         default:
    2124       35240 :             break;
    2125             :     }
    2126             : 
    2127             :     /* subPlan-s */
    2128       38004 :     if (planstate->subPlan)
    2129         176 :         ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es);
    2130             : 
    2131             :     /* end of child plans */
    2132       38004 :     if (haschildren)
    2133             :     {
    2134       18904 :         ancestors = list_delete_first(ancestors);
    2135       18904 :         ExplainCloseGroup("Plans", "Plans", false, es);
    2136             :     }
    2137             : 
    2138             :     /* in text format, undo whatever indentation we added */
    2139       38004 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    2140       37340 :         es->indent = save_indent;
    2141             : 
    2142       38004 :     ExplainCloseGroup("Plan",
    2143             :                       relationship ? NULL : "Plan",
    2144             :                       true, es);
    2145       38004 : }
    2146             : 
    2147             : /*
    2148             :  * Show the targetlist of a plan node
    2149             :  */
    2150             : static void
    2151        3660 : show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
    2152             : {
    2153        3660 :     Plan       *plan = planstate->plan;
    2154             :     List       *context;
    2155        3660 :     List       *result = NIL;
    2156             :     bool        useprefix;
    2157             :     ListCell   *lc;
    2158             : 
    2159             :     /* No work if empty tlist (this occurs eg in bitmap indexscans) */
    2160        3660 :     if (plan->targetlist == NIL)
    2161         206 :         return;
    2162             :     /* The tlist of an Append isn't real helpful, so suppress it */
    2163        3454 :     if (IsA(plan, Append))
    2164          60 :         return;
    2165             :     /* Likewise for MergeAppend and RecursiveUnion */
    2166        3394 :     if (IsA(plan, MergeAppend))
    2167          16 :         return;
    2168        3378 :     if (IsA(plan, RecursiveUnion))
    2169           8 :         return;
    2170             : 
    2171             :     /*
    2172             :      * Likewise for ForeignScan that executes a direct INSERT/UPDATE/DELETE
    2173             :      *
    2174             :      * Note: the tlist for a ForeignScan that executes a direct INSERT/UPDATE
    2175             :      * might contain subplan output expressions that are confusing in this
    2176             :      * context.  The tlist for a ForeignScan that executes a direct UPDATE/
    2177             :      * DELETE always contains "junk" target columns to identify the exact row
    2178             :      * to update or delete, which would be confusing in this context.  So, we
    2179             :      * suppress it in all the cases.
    2180             :      */
    2181        3370 :     if (IsA(plan, ForeignScan) &&
    2182         546 :         ((ForeignScan *) plan)->operation != CMD_SELECT)
    2183          56 :         return;
    2184             : 
    2185             :     /* Set up deparsing context */
    2186        3314 :     context = set_deparse_context_plan(es->deparse_cxt,
    2187             :                                        plan,
    2188             :                                        ancestors);
    2189        3314 :     useprefix = list_length(es->rtable) > 1;
    2190             : 
    2191             :     /* Deparse each result column (we now include resjunk ones) */
    2192       12334 :     foreach(lc, plan->targetlist)
    2193             :     {
    2194        9020 :         TargetEntry *tle = (TargetEntry *) lfirst(lc);
    2195             : 
    2196        9020 :         result = lappend(result,
    2197        9020 :                          deparse_expression((Node *) tle->expr, context,
    2198             :                                             useprefix, false));
    2199             :     }
    2200             : 
    2201             :     /* Print results */
    2202        3314 :     ExplainPropertyList("Output", result, es);
    2203             : }
    2204             : 
    2205             : /*
    2206             :  * Show a generic expression
    2207             :  */
    2208             : static void
    2209       17040 : show_expression(Node *node, const char *qlabel,
    2210             :                 PlanState *planstate, List *ancestors,
    2211             :                 bool useprefix, ExplainState *es)
    2212             : {
    2213             :     List       *context;
    2214             :     char       *exprstr;
    2215             : 
    2216             :     /* Set up deparsing context */
    2217       17040 :     context = set_deparse_context_plan(es->deparse_cxt,
    2218       17040 :                                        planstate->plan,
    2219             :                                        ancestors);
    2220             : 
    2221             :     /* Deparse the expression */
    2222       17040 :     exprstr = deparse_expression(node, context, useprefix, false);
    2223             : 
    2224             :     /* And add to es->str */
    2225       17040 :     ExplainPropertyText(qlabel, exprstr, es);
    2226       17040 : }
    2227             : 
    2228             : /*
    2229             :  * Show a qualifier expression (which is a List with implicit AND semantics)
    2230             :  */
    2231             : static void
    2232       45138 : show_qual(List *qual, const char *qlabel,
    2233             :           PlanState *planstate, List *ancestors,
    2234             :           bool useprefix, ExplainState *es)
    2235             : {
    2236             :     Node       *node;
    2237             : 
    2238             :     /* No work if empty qual */
    2239       45138 :     if (qual == NIL)
    2240       28170 :         return;
    2241             : 
    2242             :     /* Convert AND list to explicit AND */
    2243       16968 :     node = (Node *) make_ands_explicit(qual);
    2244             : 
    2245             :     /* And show it */
    2246       16968 :     show_expression(node, qlabel, planstate, ancestors, useprefix, es);
    2247             : }
    2248             : 
    2249             : /*
    2250             :  * Show a qualifier expression for a scan plan node
    2251             :  */
    2252             : static void
    2253       29202 : show_scan_qual(List *qual, const char *qlabel,
    2254             :                PlanState *planstate, List *ancestors,
    2255             :                ExplainState *es)
    2256             : {
    2257             :     bool        useprefix;
    2258             : 
    2259       29202 :     useprefix = (IsA(planstate->plan, SubqueryScan) || es->verbose);
    2260       29202 :     show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
    2261       29202 : }
    2262             : 
    2263             : /*
    2264             :  * Show a qualifier expression for an upper-level plan node
    2265             :  */
    2266             : static void
    2267       15936 : show_upper_qual(List *qual, const char *qlabel,
    2268             :                 PlanState *planstate, List *ancestors,
    2269             :                 ExplainState *es)
    2270             : {
    2271             :     bool        useprefix;
    2272             : 
    2273       15936 :     useprefix = (list_length(es->rtable) > 1 || es->verbose);
    2274       15936 :     show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
    2275       15936 : }
    2276             : 
    2277             : /*
    2278             :  * Show the sort keys for a Sort node.
    2279             :  */
    2280             : static void
    2281        2188 : show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
    2282             : {
    2283        2188 :     Sort       *plan = (Sort *) sortstate->ss.ps.plan;
    2284             : 
    2285        2188 :     show_sort_group_keys((PlanState *) sortstate, "Sort Key",
    2286             :                          plan->numCols, 0, plan->sortColIdx,
    2287             :                          plan->sortOperators, plan->collations,
    2288             :                          plan->nullsFirst,
    2289             :                          ancestors, es);
    2290        2188 : }
    2291             : 
    2292             : /*
    2293             :  * Show the sort keys for a IncrementalSort node.
    2294             :  */
    2295             : static void
    2296         124 : show_incremental_sort_keys(IncrementalSortState *incrsortstate,
    2297             :                            List *ancestors, ExplainState *es)
    2298             : {
    2299         124 :     IncrementalSort *plan = (IncrementalSort *) incrsortstate->ss.ps.plan;
    2300             : 
    2301         124 :     show_sort_group_keys((PlanState *) incrsortstate, "Sort Key",
    2302             :                          plan->sort.numCols, plan->nPresortedCols,
    2303             :                          plan->sort.sortColIdx,
    2304             :                          plan->sort.sortOperators, plan->sort.collations,
    2305             :                          plan->sort.nullsFirst,
    2306             :                          ancestors, es);
    2307         124 : }
    2308             : 
    2309             : /*
    2310             :  * Likewise, for a MergeAppend node.
    2311             :  */
    2312             : static void
    2313         148 : show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
    2314             :                        ExplainState *es)
    2315             : {
    2316         148 :     MergeAppend *plan = (MergeAppend *) mstate->ps.plan;
    2317             : 
    2318         148 :     show_sort_group_keys((PlanState *) mstate, "Sort Key",
    2319             :                          plan->numCols, 0, plan->sortColIdx,
    2320             :                          plan->sortOperators, plan->collations,
    2321             :                          plan->nullsFirst,
    2322             :                          ancestors, es);
    2323         148 : }
    2324             : 
    2325             : /*
    2326             :  * Show the grouping keys for an Agg node.
    2327             :  */
    2328             : static void
    2329        4132 : show_agg_keys(AggState *astate, List *ancestors,
    2330             :               ExplainState *es)
    2331             : {
    2332        4132 :     Agg        *plan = (Agg *) astate->ss.ps.plan;
    2333             : 
    2334        4132 :     if (plan->numCols > 0 || plan->groupingSets)
    2335             :     {
    2336             :         /* The key columns refer to the tlist of the child plan */
    2337        1042 :         ancestors = lcons(plan, ancestors);
    2338             : 
    2339        1042 :         if (plan->groupingSets)
    2340         106 :             show_grouping_sets(outerPlanState(astate), plan, ancestors, es);
    2341             :         else
    2342         936 :             show_sort_group_keys(outerPlanState(astate), "Group Key",
    2343             :                                  plan->numCols, 0, plan->grpColIdx,
    2344             :                                  NULL, NULL, NULL,
    2345             :                                  ancestors, es);
    2346             : 
    2347        1042 :         ancestors = list_delete_first(ancestors);
    2348             :     }
    2349        4132 : }
    2350             : 
    2351             : static void
    2352         106 : show_grouping_sets(PlanState *planstate, Agg *agg,
    2353             :                    List *ancestors, ExplainState *es)
    2354             : {
    2355             :     List       *context;
    2356             :     bool        useprefix;
    2357             :     ListCell   *lc;
    2358             : 
    2359             :     /* Set up deparsing context */
    2360         106 :     context = set_deparse_context_plan(es->deparse_cxt,
    2361         106 :                                        planstate->plan,
    2362             :                                        ancestors);
    2363         106 :     useprefix = (list_length(es->rtable) > 1 || es->verbose);
    2364             : 
    2365         106 :     ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es);
    2366             : 
    2367         106 :     show_grouping_set_keys(planstate, agg, NULL,
    2368             :                            context, useprefix, ancestors, es);
    2369             : 
    2370         300 :     foreach(lc, agg->chain)
    2371             :     {
    2372         194 :         Agg        *aggnode = lfirst(lc);
    2373         194 :         Sort       *sortnode = (Sort *) aggnode->plan.lefttree;
    2374             : 
    2375         194 :         show_grouping_set_keys(planstate, aggnode, sortnode,
    2376             :                                context, useprefix, ancestors, es);
    2377             :     }
    2378             : 
    2379         106 :     ExplainCloseGroup("Grouping Sets", "Grouping Sets", false, es);
    2380         106 : }
    2381             : 
    2382             : static void
    2383         300 : show_grouping_set_keys(PlanState *planstate,
    2384             :                        Agg *aggnode, Sort *sortnode,
    2385             :                        List *context, bool useprefix,
    2386             :                        List *ancestors, ExplainState *es)
    2387             : {
    2388         300 :     Plan       *plan = planstate->plan;
    2389             :     char       *exprstr;
    2390             :     ListCell   *lc;
    2391         300 :     List       *gsets = aggnode->groupingSets;
    2392         300 :     AttrNumber *keycols = aggnode->grpColIdx;
    2393             :     const char *keyname;
    2394             :     const char *keysetname;
    2395             : 
    2396         300 :     if (aggnode->aggstrategy == AGG_HASHED || aggnode->aggstrategy == AGG_MIXED)
    2397             :     {
    2398         184 :         keyname = "Hash Key";
    2399         184 :         keysetname = "Hash Keys";
    2400             :     }
    2401             :     else
    2402             :     {
    2403         116 :         keyname = "Group Key";
    2404         116 :         keysetname = "Group Keys";
    2405             :     }
    2406             : 
    2407         300 :     ExplainOpenGroup("Grouping Set", NULL, true, es);
    2408             : 
    2409         300 :     if (sortnode)
    2410             :     {
    2411          32 :         show_sort_group_keys(planstate, "Sort Key",
    2412             :                              sortnode->numCols, 0, sortnode->sortColIdx,
    2413             :                              sortnode->sortOperators, sortnode->collations,
    2414             :                              sortnode->nullsFirst,
    2415             :                              ancestors, es);
    2416          32 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    2417          32 :             es->indent++;
    2418             :     }
    2419             : 
    2420         300 :     ExplainOpenGroup(keysetname, keysetname, false, es);
    2421             : 
    2422         664 :     foreach(lc, gsets)
    2423             :     {
    2424         364 :         List       *result = NIL;
    2425             :         ListCell   *lc2;
    2426             : 
    2427         760 :         foreach(lc2, (List *) lfirst(lc))
    2428             :         {
    2429         396 :             Index       i = lfirst_int(lc2);
    2430         396 :             AttrNumber  keyresno = keycols[i];
    2431         396 :             TargetEntry *target = get_tle_by_resno(plan->targetlist,
    2432             :                                                    keyresno);
    2433             : 
    2434         396 :             if (!target)
    2435           0 :                 elog(ERROR, "no tlist entry for key %d", keyresno);
    2436             :             /* Deparse the expression, showing any top-level cast */
    2437         396 :             exprstr = deparse_expression((Node *) target->expr, context,
    2438             :                                          useprefix, true);
    2439             : 
    2440         396 :             result = lappend(result, exprstr);
    2441             :         }
    2442             : 
    2443         364 :         if (!result && es->format == EXPLAIN_FORMAT_TEXT)
    2444          72 :             ExplainPropertyText(keyname, "()", es);
    2445             :         else
    2446         292 :             ExplainPropertyListNested(keyname, result, es);
    2447             :     }
    2448             : 
    2449         300 :     ExplainCloseGroup(keysetname, keysetname, false, es);
    2450             : 
    2451         300 :     if (sortnode && es->format == EXPLAIN_FORMAT_TEXT)
    2452          32 :         es->indent--;
    2453             : 
    2454         300 :     ExplainCloseGroup("Grouping Set", NULL, true, es);
    2455         300 : }
    2456             : 
    2457             : /*
    2458             :  * Show the grouping keys for a Group node.
    2459             :  */
    2460             : static void
    2461          52 : show_group_keys(GroupState *gstate, List *ancestors,
    2462             :                 ExplainState *es)
    2463             : {
    2464          52 :     Group      *plan = (Group *) gstate->ss.ps.plan;
    2465             : 
    2466             :     /* The key columns refer to the tlist of the child plan */
    2467          52 :     ancestors = lcons(plan, ancestors);
    2468          52 :     show_sort_group_keys(outerPlanState(gstate), "Group Key",
    2469             :                          plan->numCols, 0, plan->grpColIdx,
    2470             :                          NULL, NULL, NULL,
    2471             :                          ancestors, es);
    2472          52 :     ancestors = list_delete_first(ancestors);
    2473          52 : }
    2474             : 
    2475             : /*
    2476             :  * Common code to show sort/group keys, which are represented in plan nodes
    2477             :  * as arrays of targetlist indexes.  If it's a sort key rather than a group
    2478             :  * key, also pass sort operators/collations/nullsFirst arrays.
    2479             :  */
    2480             : static void
    2481        3480 : show_sort_group_keys(PlanState *planstate, const char *qlabel,
    2482             :                      int nkeys, int nPresortedKeys, AttrNumber *keycols,
    2483             :                      Oid *sortOperators, Oid *collations, bool *nullsFirst,
    2484             :                      List *ancestors, ExplainState *es)
    2485             : {
    2486        3480 :     Plan       *plan = planstate->plan;
    2487             :     List       *context;
    2488        3480 :     List       *result = NIL;
    2489        3480 :     List       *resultPresorted = NIL;
    2490             :     StringInfoData sortkeybuf;
    2491             :     bool        useprefix;
    2492             :     int         keyno;
    2493             : 
    2494        3480 :     if (nkeys <= 0)
    2495           0 :         return;
    2496             : 
    2497        3480 :     initStringInfo(&sortkeybuf);
    2498             : 
    2499             :     /* Set up deparsing context */
    2500        3480 :     context = set_deparse_context_plan(es->deparse_cxt,
    2501             :                                        plan,
    2502             :                                        ancestors);
    2503        3480 :     useprefix = (list_length(es->rtable) > 1 || es->verbose);
    2504             : 
    2505        8472 :     for (keyno = 0; keyno < nkeys; keyno++)
    2506             :     {
    2507             :         /* find key expression in tlist */
    2508        4992 :         AttrNumber  keyresno = keycols[keyno];
    2509        4992 :         TargetEntry *target = get_tle_by_resno(plan->targetlist,
    2510             :                                                keyresno);
    2511             :         char       *exprstr;
    2512             : 
    2513        4992 :         if (!target)
    2514           0 :             elog(ERROR, "no tlist entry for key %d", keyresno);
    2515             :         /* Deparse the expression, showing any top-level cast */
    2516        4992 :         exprstr = deparse_expression((Node *) target->expr, context,
    2517             :                                      useprefix, true);
    2518        4992 :         resetStringInfo(&sortkeybuf);
    2519        4992 :         appendStringInfoString(&sortkeybuf, exprstr);
    2520             :         /* Append sort order information, if relevant */
    2521        4992 :         if (sortOperators != NULL)
    2522       14192 :             show_sortorder_options(&sortkeybuf,
    2523        3548 :                                    (Node *) target->expr,
    2524        3548 :                                    sortOperators[keyno],
    2525        3548 :                                    collations[keyno],
    2526        3548 :                                    nullsFirst[keyno]);
    2527             :         /* Emit one property-list item per sort key */
    2528        4992 :         result = lappend(result, pstrdup(sortkeybuf.data));
    2529        4992 :         if (keyno < nPresortedKeys)
    2530         128 :             resultPresorted = lappend(resultPresorted, exprstr);
    2531             :     }
    2532             : 
    2533        3480 :     ExplainPropertyList(qlabel, result, es);
    2534        3480 :     if (nPresortedKeys > 0)
    2535         124 :         ExplainPropertyList("Presorted Key", resultPresorted, es);
    2536             : }
    2537             : 
    2538             : /*
    2539             :  * Append nondefault characteristics of the sort ordering of a column to buf
    2540             :  * (collation, direction, NULLS FIRST/LAST)
    2541             :  */
    2542             : static void
    2543        3548 : show_sortorder_options(StringInfo buf, Node *sortexpr,
    2544             :                        Oid sortOperator, Oid collation, bool nullsFirst)
    2545             : {
    2546        3548 :     Oid         sortcoltype = exprType(sortexpr);
    2547        3548 :     bool        reverse = false;
    2548             :     TypeCacheEntry *typentry;
    2549             : 
    2550        3548 :     typentry = lookup_type_cache(sortcoltype,
    2551             :                                  TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
    2552             : 
    2553             :     /*
    2554             :      * Print COLLATE if it's not default for the column's type.  There are
    2555             :      * some cases where this is redundant, eg if expression is a column whose
    2556             :      * declared collation is that collation, but it's hard to distinguish that
    2557             :      * here (and arguably, printing COLLATE explicitly is a good idea anyway
    2558             :      * in such cases).
    2559             :      */
    2560        3548 :     if (OidIsValid(collation) && collation != get_typcollation(sortcoltype))
    2561             :     {
    2562          18 :         char       *collname = get_collation_name(collation);
    2563             : 
    2564          18 :         if (collname == NULL)
    2565           0 :             elog(ERROR, "cache lookup failed for collation %u", collation);
    2566          18 :         appendStringInfo(buf, " COLLATE %s", quote_identifier(collname));
    2567             :     }
    2568             : 
    2569             :     /* Print direction if not ASC, or USING if non-default sort operator */
    2570        3548 :     if (sortOperator == typentry->gt_opr)
    2571             :     {
    2572          96 :         appendStringInfoString(buf, " DESC");
    2573          96 :         reverse = true;
    2574             :     }
    2575        3452 :     else if (sortOperator != typentry->lt_opr)
    2576             :     {
    2577           8 :         char       *opname = get_opname(sortOperator);
    2578             : 
    2579           8 :         if (opname == NULL)
    2580           0 :             elog(ERROR, "cache lookup failed for operator %u", sortOperator);
    2581           8 :         appendStringInfo(buf, " USING %s", opname);
    2582             :         /* Determine whether operator would be considered ASC or DESC */
    2583           8 :         (void) get_equality_op_for_ordering_op(sortOperator, &reverse);
    2584             :     }
    2585             : 
    2586             :     /* Add NULLS FIRST/LAST only if it wouldn't be default */
    2587        3548 :     if (nullsFirst && !reverse)
    2588             :     {
    2589           4 :         appendStringInfoString(buf, " NULLS FIRST");
    2590             :     }
    2591        3544 :     else if (!nullsFirst && reverse)
    2592             :     {
    2593           0 :         appendStringInfoString(buf, " NULLS LAST");
    2594             :     }
    2595        3548 : }
    2596             : 
    2597             : /*
    2598             :  * Show TABLESAMPLE properties
    2599             :  */
    2600             : static void
    2601          48 : show_tablesample(TableSampleClause *tsc, PlanState *planstate,
    2602             :                  List *ancestors, ExplainState *es)
    2603             : {
    2604             :     List       *context;
    2605             :     bool        useprefix;
    2606             :     char       *method_name;
    2607          48 :     List       *params = NIL;
    2608             :     char       *repeatable;
    2609             :     ListCell   *lc;
    2610             : 
    2611             :     /* Set up deparsing context */
    2612          48 :     context = set_deparse_context_plan(es->deparse_cxt,
    2613          48 :                                        planstate->plan,
    2614             :                                        ancestors);
    2615          48 :     useprefix = list_length(es->rtable) > 1;
    2616             : 
    2617             :     /* Get the tablesample method name */
    2618          48 :     method_name = get_func_name(tsc->tsmhandler);
    2619             : 
    2620             :     /* Deparse parameter expressions */
    2621          96 :     foreach(lc, tsc->args)
    2622             :     {
    2623          48 :         Node       *arg = (Node *) lfirst(lc);
    2624             : 
    2625          48 :         params = lappend(params,
    2626          48 :                          deparse_expression(arg, context,
    2627             :                                             useprefix, false));
    2628             :     }
    2629          48 :     if (tsc->repeatable)
    2630           8 :         repeatable = deparse_expression((Node *) tsc->repeatable, context,
    2631             :                                         useprefix, false);
    2632             :     else
    2633          40 :         repeatable = NULL;
    2634             : 
    2635             :     /* Print results */
    2636          48 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    2637             :     {
    2638          48 :         bool        first = true;
    2639             : 
    2640          48 :         ExplainIndentText(es);
    2641          48 :         appendStringInfo(es->str, "Sampling: %s (", method_name);
    2642          96 :         foreach(lc, params)
    2643             :         {
    2644          48 :             if (!first)
    2645           0 :                 appendStringInfoString(es->str, ", ");
    2646          48 :             appendStringInfoString(es->str, (const char *) lfirst(lc));
    2647          48 :             first = false;
    2648             :         }
    2649          48 :         appendStringInfoChar(es->str, ')');
    2650          48 :         if (repeatable)
    2651           8 :             appendStringInfo(es->str, " REPEATABLE (%s)", repeatable);
    2652          48 :         appendStringInfoChar(es->str, '\n');
    2653             :     }
    2654             :     else
    2655             :     {
    2656           0 :         ExplainPropertyText("Sampling Method", method_name, es);
    2657           0 :         ExplainPropertyList("Sampling Parameters", params, es);
    2658           0 :         if (repeatable)
    2659           0 :             ExplainPropertyText("Repeatable Seed", repeatable, es);
    2660             :     }
    2661          48 : }
    2662             : 
    2663             : /*
    2664             :  * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
    2665             :  */
    2666             : static void
    2667        2188 : show_sort_info(SortState *sortstate, ExplainState *es)
    2668             : {
    2669        2188 :     if (!es->analyze)
    2670        2152 :         return;
    2671             : 
    2672          36 :     if (sortstate->sort_Done && sortstate->tuplesortstate != NULL)
    2673             :     {
    2674          36 :         Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;
    2675             :         TuplesortInstrumentation stats;
    2676             :         const char *sortMethod;
    2677             :         const char *spaceType;
    2678             :         long        spaceUsed;
    2679             : 
    2680          36 :         tuplesort_get_stats(state, &stats);
    2681          36 :         sortMethod = tuplesort_method_name(stats.sortMethod);
    2682          36 :         spaceType = tuplesort_space_type_name(stats.spaceType);
    2683          36 :         spaceUsed = stats.spaceUsed;
    2684             : 
    2685          36 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    2686             :         {
    2687          16 :             ExplainIndentText(es);
    2688          16 :             appendStringInfo(es->str, "Sort Method: %s  %s: %ldkB\n",
    2689             :                              sortMethod, spaceType, spaceUsed);
    2690             :         }
    2691             :         else
    2692             :         {
    2693          20 :             ExplainPropertyText("Sort Method", sortMethod, es);
    2694          20 :             ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es);
    2695          20 :             ExplainPropertyText("Sort Space Type", spaceType, es);
    2696             :         }
    2697             :     }
    2698             : 
    2699             :     /*
    2700             :      * You might think we should just skip this stanza entirely when
    2701             :      * es->hide_workers is true, but then we'd get no sort-method output at
    2702             :      * all.  We have to make it look like worker 0's data is top-level data.
    2703             :      * This is easily done by just skipping the OpenWorker/CloseWorker calls.
    2704             :      * Currently, we don't worry about the possibility that there are multiple
    2705             :      * workers in such a case; if there are, duplicate output fields will be
    2706             :      * emitted.
    2707             :      */
    2708          36 :     if (sortstate->shared_info != NULL)
    2709             :     {
    2710             :         int         n;
    2711             : 
    2712          40 :         for (n = 0; n < sortstate->shared_info->num_workers; n++)
    2713             :         {
    2714             :             TuplesortInstrumentation *sinstrument;
    2715             :             const char *sortMethod;
    2716             :             const char *spaceType;
    2717             :             long        spaceUsed;
    2718             : 
    2719          32 :             sinstrument = &sortstate->shared_info->sinstrument[n];
    2720          32 :             if (sinstrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS)
    2721           0 :                 continue;       /* ignore any unfilled slots */
    2722          32 :             sortMethod = tuplesort_method_name(sinstrument->sortMethod);
    2723          32 :             spaceType = tuplesort_space_type_name(sinstrument->spaceType);
    2724          32 :             spaceUsed = sinstrument->spaceUsed;
    2725             : 
    2726          32 :             if (es->workers_state)
    2727          32 :                 ExplainOpenWorker(n, es);
    2728             : 
    2729          32 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    2730             :             {
    2731          16 :                 ExplainIndentText(es);
    2732          16 :                 appendStringInfo(es->str,
    2733             :                                  "Sort Method: %s  %s: %ldkB\n",
    2734             :                                  sortMethod, spaceType, spaceUsed);
    2735             :             }
    2736             :             else
    2737             :             {
    2738          16 :                 ExplainPropertyText("Sort Method", sortMethod, es);
    2739          16 :                 ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es);
    2740          16 :                 ExplainPropertyText("Sort Space Type", spaceType, es);
    2741             :             }
    2742             : 
    2743          32 :             if (es->workers_state)
    2744          32 :                 ExplainCloseWorker(n, es);
    2745             :         }
    2746             :     }
    2747             : }
    2748             : 
    2749             : /*
    2750             :  * Incremental sort nodes sort in (a potentially very large number of) batches,
    2751             :  * so EXPLAIN ANALYZE needs to roll up the tuplesort stats from each batch into
    2752             :  * an intelligible summary.
    2753             :  *
    2754             :  * This function is used for both a non-parallel node and each worker in a
    2755             :  * parallel incremental sort node.
    2756             :  */
    2757             : static void
    2758          36 : show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo,
    2759             :                                  const char *groupLabel, bool indent, ExplainState *es)
    2760             : {
    2761             :     ListCell   *methodCell;
    2762          36 :     List       *methodNames = NIL;
    2763             : 
    2764             :     /* Generate a list of sort methods used across all groups. */
    2765         180 :     for (int bit = 0; bit < NUM_TUPLESORTMETHODS; bit++)
    2766             :     {
    2767         144 :         TuplesortMethod sortMethod = (1 << bit);
    2768             : 
    2769         144 :         if (groupInfo->sortMethods & sortMethod)
    2770             :         {
    2771          60 :             const char *methodName = tuplesort_method_name(sortMethod);
    2772             : 
    2773          60 :             methodNames = lappend(methodNames, unconstify(char *, methodName));
    2774             :         }
    2775             :     }
    2776             : 
    2777          36 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    2778             :     {
    2779          12 :         if (indent)
    2780          12 :             appendStringInfoSpaces(es->str, es->indent * 2);
    2781          12 :         appendStringInfo(es->str, "%s Groups: " INT64_FORMAT "  Sort Method", groupLabel,
    2782             :                          groupInfo->groupCount);
    2783             :         /* plural/singular based on methodNames size */
    2784          12 :         if (list_length(methodNames) > 1)
    2785           8 :             appendStringInfo(es->str, "s: ");
    2786             :         else
    2787           4 :             appendStringInfo(es->str, ": ");
    2788          32 :         foreach(methodCell, methodNames)
    2789             :         {
    2790          20 :             appendStringInfo(es->str, "%s", (char *) methodCell->ptr_value);
    2791          20 :             if (foreach_current_index(methodCell) < list_length(methodNames) - 1)
    2792           8 :                 appendStringInfo(es->str, ", ");
    2793             :         }
    2794             : 
    2795          12 :         if (groupInfo->maxMemorySpaceUsed > 0)
    2796             :         {
    2797          12 :             long        avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount;
    2798             :             const char *spaceTypeName;
    2799             : 
    2800          12 :             spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY);
    2801          12 :             appendStringInfo(es->str, "  Average %s: %ldkB  Peak %s: %ldkB",
    2802             :                              spaceTypeName, avgSpace,
    2803             :                              spaceTypeName, groupInfo->maxMemorySpaceUsed);
    2804             :         }
    2805             : 
    2806          12 :         if (groupInfo->maxDiskSpaceUsed > 0)
    2807             :         {
    2808           0 :             long        avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount;
    2809             : 
    2810             :             const char *spaceTypeName;
    2811             : 
    2812           0 :             spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_DISK);
    2813           0 :             appendStringInfo(es->str, "  Average %s: %ldkB  Peak %s: %ldkB",
    2814             :                              spaceTypeName, avgSpace,
    2815             :                              spaceTypeName, groupInfo->maxDiskSpaceUsed);
    2816             :         }
    2817             :     }
    2818             :     else
    2819             :     {
    2820             :         StringInfoData groupName;
    2821             : 
    2822          24 :         initStringInfo(&groupName);
    2823          24 :         appendStringInfo(&groupName, "%s Groups", groupLabel);
    2824          24 :         ExplainOpenGroup("Incremental Sort Groups", groupName.data, true, es);
    2825          24 :         ExplainPropertyInteger("Group Count", NULL, groupInfo->groupCount, es);
    2826             : 
    2827          24 :         ExplainPropertyList("Sort Methods Used", methodNames, es);
    2828             : 
    2829          24 :         if (groupInfo->maxMemorySpaceUsed > 0)
    2830             :         {
    2831          24 :             long        avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount;
    2832             :             const char *spaceTypeName;
    2833             :             StringInfoData memoryName;
    2834             : 
    2835          24 :             spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY);
    2836          24 :             initStringInfo(&memoryName);
    2837          24 :             appendStringInfo(&memoryName, "Sort Space %s", spaceTypeName);
    2838          24 :             ExplainOpenGroup("Sort Space", memoryName.data, true, es);
    2839             : 
    2840          24 :             ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es);
    2841          24 :             ExplainPropertyInteger("Peak Sort Space Used", "kB",
    2842             :                                    groupInfo->maxMemorySpaceUsed, es);
    2843             : 
    2844          24 :             ExplainCloseGroup("Sort Spaces", memoryName.data, true, es);
    2845             :         }
    2846          24 :         if (groupInfo->maxDiskSpaceUsed > 0)
    2847             :         {
    2848           0 :             long        avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount;
    2849             :             const char *spaceTypeName;
    2850             :             StringInfoData diskName;
    2851             : 
    2852           0 :             spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_DISK);
    2853           0 :             initStringInfo(&diskName);
    2854           0 :             appendStringInfo(&diskName, "Sort Space %s", spaceTypeName);
    2855           0 :             ExplainOpenGroup("Sort Space", diskName.data, true, es);
    2856             : 
    2857           0 :             ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es);
    2858           0 :             ExplainPropertyInteger("Peak Sort Space Used", "kB",
    2859             :                                    groupInfo->maxDiskSpaceUsed, es);
    2860             : 
    2861           0 :             ExplainCloseGroup("Sort Spaces", diskName.data, true, es);
    2862             :         }
    2863             : 
    2864          24 :         ExplainCloseGroup("Incremental Sort Groups", groupName.data, true, es);
    2865             :     }
    2866          36 : }
    2867             : 
    2868             : /*
    2869             :  * If it's EXPLAIN ANALYZE, show tuplesort stats for an incremental sort node
    2870             :  */
    2871             : static void
    2872         124 : show_incremental_sort_info(IncrementalSortState *incrsortstate,
    2873             :                            ExplainState *es)
    2874             : {
    2875             :     IncrementalSortGroupInfo *fullsortGroupInfo;
    2876             :     IncrementalSortGroupInfo *prefixsortGroupInfo;
    2877             : 
    2878         124 :     fullsortGroupInfo = &incrsortstate->incsort_info.fullsortGroupInfo;
    2879             : 
    2880         124 :     if (!es->analyze)
    2881         100 :         return;
    2882             : 
    2883             :     /*
    2884             :      * Since we never have any prefix groups unless we've first sorted a full
    2885             :      * groups and transitioned modes (copying the tuples into a prefix group),
    2886             :      * we don't need to do anything if there were 0 full groups.
    2887             :      *
    2888             :      * We still have to continue after this block if there are no full groups,
    2889             :      * though, since it's possible that we have workers that did real work
    2890             :      * even if the leader didn't participate.
    2891             :      */
    2892          24 :     if (fullsortGroupInfo->groupCount > 0)
    2893             :     {
    2894          24 :         show_incremental_sort_group_info(fullsortGroupInfo, "Full-sort", true, es);
    2895          24 :         prefixsortGroupInfo = &incrsortstate->incsort_info.prefixsortGroupInfo;
    2896          24 :         if (prefixsortGroupInfo->groupCount > 0)
    2897             :         {
    2898          12 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    2899           4 :                 appendStringInfo(es->str, "\n");
    2900          12 :             show_incremental_sort_group_info(prefixsortGroupInfo, "Pre-sorted", true, es);
    2901             :         }
    2902          24 :         if (es->format == EXPLAIN_FORMAT_TEXT)
    2903           8 :             appendStringInfo(es->str, "\n");
    2904             :     }
    2905             : 
    2906          24 :     if (incrsortstate->shared_info != NULL)
    2907             :     {
    2908             :         int         n;
    2909             :         bool        indent_first_line;
    2910             : 
    2911           0 :         for (n = 0; n < incrsortstate->shared_info->num_workers; n++)
    2912             :         {
    2913           0 :             IncrementalSortInfo *incsort_info =
    2914           0 :             &incrsortstate->shared_info->sinfo[n];
    2915             : 
    2916             :             /*
    2917             :              * If a worker hasn't processed any sort groups at all, then
    2918             :              * exclude it from output since it either didn't launch or didn't
    2919             :              * contribute anything meaningful.
    2920             :              */
    2921           0 :             fullsortGroupInfo = &incsort_info->fullsortGroupInfo;
    2922             : 
    2923             :             /*
    2924             :              * Since we never have any prefix groups unless we've first sorted
    2925             :              * a full groups and transitioned modes (copying the tuples into a
    2926             :              * prefix group), we don't need to do anything if there were 0
    2927             :              * full groups.
    2928             :              */
    2929           0 :             if (fullsortGroupInfo->groupCount == 0)
    2930           0 :                 continue;
    2931             : 
    2932           0 :             if (es->workers_state)
    2933           0 :                 ExplainOpenWorker(n, es);
    2934             : 
    2935           0 :             indent_first_line = es->workers_state == NULL || es->verbose;
    2936           0 :             show_incremental_sort_group_info(fullsortGroupInfo, "Full-sort",
    2937             :                                              indent_first_line, es);
    2938           0 :             prefixsortGroupInfo = &incsort_info->prefixsortGroupInfo;
    2939           0 :             if (prefixsortGroupInfo->groupCount > 0)
    2940             :             {
    2941           0 :                 if (es->format == EXPLAIN_FORMAT_TEXT)
    2942           0 :                     appendStringInfo(es->str, "\n");
    2943           0 :                 show_incremental_sort_group_info(prefixsortGroupInfo, "Pre-sorted", true, es);
    2944             :             }
    2945           0 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    2946           0 :                 appendStringInfo(es->str, "\n");
    2947             : 
    2948           0 :             if (es->workers_state)
    2949           0 :                 ExplainCloseWorker(n, es);
    2950             :         }
    2951             :     }
    2952             : }
    2953             : 
    2954             : /*
    2955             :  * Show information on hash buckets/batches.
    2956             :  */
    2957             : static void
    2958        1948 : show_hash_info(HashState *hashstate, ExplainState *es)
    2959             : {
    2960        1948 :     HashInstrumentation hinstrument = {0};
    2961             : 
    2962             :     /*
    2963             :      * Collect stats from the local process, even when it's a parallel query.
    2964             :      * In a parallel query, the leader process may or may not have run the
    2965             :      * hash join, and even if it did it may not have built a hash table due to
    2966             :      * timing (if it started late it might have seen no tuples in the outer
    2967             :      * relation and skipped building the hash table).  Therefore we have to be
    2968             :      * prepared to get instrumentation data from all participants.
    2969             :      */
    2970        1948 :     if (hashstate->hinstrument)
    2971          72 :         memcpy(&hinstrument, hashstate->hinstrument,
    2972             :                sizeof(HashInstrumentation));
    2973             : 
    2974             :     /*
    2975             :      * Merge results from workers.  In the parallel-oblivious case, the
    2976             :      * results from all participants should be identical, except where
    2977             :      * participants didn't run the join at all so have no data.  In the
    2978             :      * parallel-aware case, we need to consider all the results.  Each worker
    2979             :      * may have seen a different subset of batches and we want to report the
    2980             :      * highest memory usage across all batches.  We take the maxima of other
    2981             :      * values too, for the same reasons as in ExecHashAccumInstrumentation.
    2982             :      */
    2983        1948 :     if (hashstate->shared_info)
    2984             :     {
    2985          56 :         SharedHashInfo *shared_info = hashstate->shared_info;
    2986             :         int         i;
    2987             : 
    2988         160 :         for (i = 0; i < shared_info->num_workers; ++i)
    2989             :         {
    2990         104 :             HashInstrumentation *worker_hi = &shared_info->hinstrument[i];
    2991             : 
    2992         104 :             hinstrument.nbuckets = Max(hinstrument.nbuckets,
    2993             :                                        worker_hi->nbuckets);
    2994         104 :             hinstrument.nbuckets_original = Max(hinstrument.nbuckets_original,
    2995             :                                                 worker_hi->nbuckets_original);
    2996         104 :             hinstrument.nbatch = Max(hinstrument.nbatch,
    2997             :                                      worker_hi->nbatch);
    2998         104 :             hinstrument.nbatch_original = Max(hinstrument.nbatch_original,
    2999             :                                               worker_hi->nbatch_original);
    3000         104 :             hinstrument.space_peak = Max(hinstrument.space_peak,
    3001             :                                          worker_hi->space_peak);
    3002             :         }
    3003             :     }
    3004             : 
    3005        1948 :     if (hinstrument.nbatch > 0)
    3006             :     {
    3007          72 :         long        spacePeakKb = (hinstrument.space_peak + 1023) / 1024;
    3008             : 
    3009          72 :         if (es->format != EXPLAIN_FORMAT_TEXT)
    3010             :         {
    3011          72 :             ExplainPropertyInteger("Hash Buckets", NULL,
    3012          72 :                                    hinstrument.nbuckets, es);
    3013          72 :             ExplainPropertyInteger("Original Hash Buckets", NULL,
    3014          72 :                                    hinstrument.nbuckets_original, es);
    3015          72 :             ExplainPropertyInteger("Hash Batches", NULL,
    3016          72 :                                    hinstrument.nbatch, es);
    3017          72 :             ExplainPropertyInteger("Original Hash Batches", NULL,
    3018          72 :                                    hinstrument.nbatch_original, es);
    3019          72 :             ExplainPropertyInteger("Peak Memory Usage", "kB",
    3020             :                                    spacePeakKb, es);
    3021             :         }
    3022           0 :         else if (hinstrument.nbatch_original != hinstrument.nbatch ||
    3023           0 :                  hinstrument.nbuckets_original != hinstrument.nbuckets)
    3024             :         {
    3025           0 :             ExplainIndentText(es);
    3026           0 :             appendStringInfo(es->str,
    3027             :                              "Buckets: %d (originally %d)  Batches: %d (originally %d)  Memory Usage: %ldkB\n",
    3028             :                              hinstrument.nbuckets,
    3029             :                              hinstrument.nbuckets_original,
    3030             :                              hinstrument.nbatch,
    3031             :                              hinstrument.nbatch_original,
    3032             :                              spacePeakKb);
    3033             :         }
    3034             :         else
    3035             :         {
    3036           0 :             ExplainIndentText(es);
    3037           0 :             appendStringInfo(es->str,
    3038             :                              "Buckets: %d  Batches: %d  Memory Usage: %ldkB\n",
    3039             :                              hinstrument.nbuckets, hinstrument.nbatch,
    3040             :                              spacePeakKb);
    3041             :         }
    3042             :     }
    3043        1948 : }
    3044             : 
    3045             : /*
    3046             :  * Show information on hash aggregate memory usage and batches.
    3047             :  */
    3048             : static void
    3049        4132 : show_hashagg_info(AggState *aggstate, ExplainState *es)
    3050             : {
    3051        4132 :     Agg        *agg = (Agg *) aggstate->ss.ps.plan;
    3052        4132 :     int64       memPeakKb = (aggstate->hash_mem_peak + 1023) / 1024;
    3053             : 
    3054             :     Assert(IsA(aggstate, AggState));
    3055             : 
    3056        4132 :     if (agg->aggstrategy != AGG_HASHED &&
    3057        3388 :         agg->aggstrategy != AGG_MIXED)
    3058        3340 :         return;
    3059             : 
    3060         792 :     if (es->costs && aggstate->hash_planned_partitions > 0)
    3061             :     {
    3062           0 :         ExplainPropertyInteger("Planned Partitions", NULL,
    3063           0 :                                aggstate->hash_planned_partitions, es);
    3064             :     }
    3065             : 
    3066         792 :     if (!es->analyze)
    3067         708 :         return;
    3068             : 
    3069             :     /* EXPLAIN ANALYZE */
    3070          84 :     ExplainPropertyInteger("Peak Memory Usage", "kB", memPeakKb, es);
    3071          84 :     if (aggstate->hash_batches_used > 0)
    3072             :     {
    3073           0 :         ExplainPropertyInteger("Disk Usage", "kB",
    3074           0 :                                aggstate->hash_disk_used, es);
    3075           0 :         ExplainPropertyInteger("HashAgg Batches", NULL,
    3076           0 :                                aggstate->hash_batches_used, es);
    3077             :     }
    3078             : }
    3079             : 
    3080             : /*
    3081             :  * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
    3082             :  */
    3083             : static void
    3084         316 : show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es)
    3085             : {
    3086         316 :     if (es->format != EXPLAIN_FORMAT_TEXT)
    3087             :     {
    3088          40 :         ExplainPropertyInteger("Exact Heap Blocks", NULL,
    3089             :                                planstate->exact_pages, es);
    3090          40 :         ExplainPropertyInteger("Lossy Heap Blocks", NULL,
    3091             :                                planstate->lossy_pages, es);
    3092             :     }
    3093             :     else
    3094             :     {
    3095         276 :         if (planstate->exact_pages > 0 || planstate->lossy_pages > 0)
    3096             :         {
    3097         184 :             ExplainIndentText(es);
    3098         184 :             appendStringInfoString(es->str, "Heap Blocks:");
    3099         184 :             if (planstate->exact_pages > 0)
    3100         184 :                 appendStringInfo(es->str, " exact=%ld", planstate->exact_pages);
    3101         184 :             if (planstate->lossy_pages > 0)
    3102           0 :                 appendStringInfo(es->str, " lossy=%ld", planstate->lossy_pages);
    3103         184 :             appendStringInfoChar(es->str, '\n');
    3104             :         }
    3105             :     }
    3106         316 : }
    3107             : 
    3108             : /*
    3109             :  * If it's EXPLAIN ANALYZE, show instrumentation information for a plan node
    3110             :  *
    3111             :  * "which" identifies which instrumentation counter to print
    3112             :  */
    3113             : static void
    3114       12402 : show_instrumentation_count(const char *qlabel, int which,
    3115             :                            PlanState *planstate, ExplainState *es)
    3116             : {
    3117             :     double      nfiltered;
    3118             :     double      nloops;
    3119             : 
    3120       12402 :     if (!es->analyze || !planstate->instrument)
    3121       10662 :         return;
    3122             : 
    3123        1740 :     if (which == 2)
    3124         680 :         nfiltered = planstate->instrument->nfiltered2;
    3125             :     else
    3126        1060 :         nfiltered = planstate->instrument->nfiltered1;
    3127        1740 :     nloops = planstate->instrument->nloops;
    3128             : 
    3129             :     /* In text mode, suppress zero counts; they're not interesting enough */
    3130        1740 :     if (nfiltered > 0 || es->format != EXPLAIN_FORMAT_TEXT)
    3131             :     {
    3132         536 :         if (nloops > 0)
    3133         536 :             ExplainPropertyFloat(qlabel, NULL, nfiltered / nloops, 0, es);
    3134             :         else
    3135           0 :             ExplainPropertyFloat(qlabel, NULL, 0.0, 0, es);
    3136             :     }
    3137             : }
    3138             : 
    3139             : /*
    3140             :  * Show extra information for a ForeignScan node.
    3141             :  */
    3142             : static void
    3143         598 : show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
    3144             : {
    3145         598 :     FdwRoutine *fdwroutine = fsstate->fdwroutine;
    3146             : 
    3147             :     /* Let the FDW emit whatever fields it wants */
    3148         598 :     if (((ForeignScan *) fsstate->ss.ps.plan)->operation != CMD_SELECT)
    3149             :     {
    3150          56 :         if (fdwroutine->ExplainDirectModify != NULL)
    3151          56 :             fdwroutine->ExplainDirectModify(fsstate, es);
    3152             :     }
    3153             :     else
    3154             :     {
    3155         542 :         if (fdwroutine->ExplainForeignScan != NULL)
    3156         542 :             fdwroutine->ExplainForeignScan(fsstate, es);
    3157             :     }
    3158         598 : }
    3159             : 
    3160             : /*
    3161             :  * Show initplan params evaluated at Gather or Gather Merge node.
    3162             :  */
    3163             : static void
    3164          16 : show_eval_params(Bitmapset *bms_params, ExplainState *es)
    3165             : {
    3166          16 :     int         paramid = -1;
    3167          16 :     List       *params = NIL;
    3168             : 
    3169             :     Assert(bms_params);
    3170             : 
    3171          36 :     while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
    3172             :     {
    3173             :         char        param[32];
    3174             : 
    3175          20 :         snprintf(param, sizeof(param), "$%d", paramid);
    3176          20 :         params = lappend(params, pstrdup(param));
    3177             :     }
    3178             : 
    3179          16 :     if (params)
    3180          16 :         ExplainPropertyList("Params Evaluated", params, es);
    3181          16 : }
    3182             : 
    3183             : /*
    3184             :  * Fetch the name of an index in an EXPLAIN
    3185             :  *
    3186             :  * We allow plugins to get control here so that plans involving hypothetical
    3187             :  * indexes can be explained.
    3188             :  */
    3189             : static const char *
    3190        5068 : explain_get_index_name(Oid indexId)
    3191             : {
    3192             :     const char *result;
    3193             : 
    3194        5068 :     if (explain_get_index_name_hook)
    3195           0 :         result = (*explain_get_index_name_hook) (indexId);
    3196             :     else
    3197        5068 :         result = NULL;
    3198        5068 :     if (result == NULL)
    3199             :     {
    3200             :         /* default behavior: look in the catalogs and quote it */
    3201        5068 :         result = get_rel_name(indexId);
    3202        5068 :         if (result == NULL)
    3203           0 :             elog(ERROR, "cache lookup failed for index %u", indexId);
    3204        5068 :         result = quote_identifier(result);
    3205             :     }
    3206        5068 :     return result;
    3207             : }
    3208             : 
    3209             : /*
    3210             :  * Show buffer usage details.
    3211             :  */
    3212             : static void
    3213          80 : show_buffer_usage(ExplainState *es, const BufferUsage *usage)
    3214             : {
    3215          80 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    3216             :     {
    3217          20 :         bool        has_shared = (usage->shared_blks_hit > 0 ||
    3218           4 :                                   usage->shared_blks_read > 0 ||
    3219          16 :                                   usage->shared_blks_dirtied > 0 ||
    3220           4 :                                   usage->shared_blks_written > 0);
    3221          24 :         bool        has_local = (usage->local_blks_hit > 0 ||
    3222           8 :                                  usage->local_blks_read > 0 ||
    3223          24 :                                  usage->local_blks_dirtied > 0 ||
    3224           8 :                                  usage->local_blks_written > 0);
    3225          16 :         bool        has_temp = (usage->temp_blks_read > 0 ||
    3226           8 :                                 usage->temp_blks_written > 0);
    3227          16 :         bool        has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) ||
    3228           8 :                                   !INSTR_TIME_IS_ZERO(usage->blk_write_time));
    3229             : 
    3230             :         /* Show only positive counter values. */
    3231           8 :         if (has_shared || has_local || has_temp)
    3232             :         {
    3233           4 :             ExplainIndentText(es);
    3234           4 :             appendStringInfoString(es->str, "Buffers:");
    3235             : 
    3236           4 :             if (has_shared)
    3237             :             {
    3238           4 :                 appendStringInfoString(es->str, " shared");
    3239           4 :                 if (usage->shared_blks_hit > 0)
    3240           4 :                     appendStringInfo(es->str, " hit=%ld",
    3241             :                                      usage->shared_blks_hit);
    3242           4 :                 if (usage->shared_blks_read > 0)
    3243           0 :                     appendStringInfo(es->str, " read=%ld",
    3244             :                                      usage->shared_blks_read);
    3245           4 :                 if (usage->shared_blks_dirtied > 0)
    3246           0 :                     appendStringInfo(es->str, " dirtied=%ld",
    3247             :                                      usage->shared_blks_dirtied);
    3248           4 :                 if (usage->shared_blks_written > 0)
    3249           0 :                     appendStringInfo(es->str, " written=%ld",
    3250             :                                      usage->shared_blks_written);
    3251           4 :                 if (has_local || has_temp)
    3252           0 :                     appendStringInfoChar(es->str, ',');
    3253             :             }
    3254           4 :             if (has_local)
    3255             :             {
    3256           0 :                 appendStringInfoString(es->str, " local");
    3257           0 :                 if (usage->local_blks_hit > 0)
    3258           0 :                     appendStringInfo(es->str, " hit=%ld",
    3259             :                                      usage->local_blks_hit);
    3260           0 :                 if (usage->local_blks_read > 0)
    3261           0 :                     appendStringInfo(es->str, " read=%ld",
    3262             :                                      usage->local_blks_read);
    3263           0 :                 if (usage->local_blks_dirtied > 0)
    3264           0 :                     appendStringInfo(es->str, " dirtied=%ld",
    3265             :                                      usage->local_blks_dirtied);
    3266           0 :                 if (usage->local_blks_written > 0)
    3267           0 :                     appendStringInfo(es->str, " written=%ld",
    3268             :                                      usage->local_blks_written);
    3269           0 :                 if (has_temp)
    3270           0 :                     appendStringInfoChar(es->str, ',');
    3271             :             }
    3272           4 :             if (has_temp)
    3273             :             {
    3274           0 :                 appendStringInfoString(es->str, " temp");
    3275           0 :                 if (usage->temp_blks_read > 0)
    3276           0 :                     appendStringInfo(es->str, " read=%ld",
    3277             :                                      usage->temp_blks_read);
    3278           0 :                 if (usage->temp_blks_written > 0)
    3279           0 :                     appendStringInfo(es->str, " written=%ld",
    3280             :                                      usage->temp_blks_written);
    3281             :             }
    3282           4 :             appendStringInfoChar(es->str, '\n');
    3283             :         }
    3284             : 
    3285             :         /* As above, show only positive counter values. */
    3286           8 :         if (has_timing)
    3287             :         {
    3288           0 :             ExplainIndentText(es);
    3289           0 :             appendStringInfoString(es->str, "I/O Timings:");
    3290           0 :             if (!INSTR_TIME_IS_ZERO(usage->blk_read_time))
    3291           0 :                 appendStringInfo(es->str, " read=%0.3f",
    3292           0 :                                  INSTR_TIME_GET_MILLISEC(usage->blk_read_time));
    3293           0 :             if (!INSTR_TIME_IS_ZERO(usage->blk_write_time))
    3294           0 :                 appendStringInfo(es->str, " write=%0.3f",
    3295           0 :                                  INSTR_TIME_GET_MILLISEC(usage->blk_write_time));
    3296           0 :             appendStringInfoChar(es->str, '\n');
    3297             :         }
    3298             :     }
    3299             :     else
    3300             :     {
    3301          72 :         ExplainPropertyInteger("Shared Hit Blocks", NULL,
    3302             :                                usage->shared_blks_hit, es);
    3303          72 :         ExplainPropertyInteger("Shared Read Blocks", NULL,
    3304             :                                usage->shared_blks_read, es);
    3305          72 :         ExplainPropertyInteger("Shared Dirtied Blocks", NULL,
    3306             :                                usage->shared_blks_dirtied, es);
    3307          72 :         ExplainPropertyInteger("Shared Written Blocks", NULL,
    3308             :                                usage->shared_blks_written, es);
    3309          72 :         ExplainPropertyInteger("Local Hit Blocks", NULL,
    3310             :                                usage->local_blks_hit, es);
    3311          72 :         ExplainPropertyInteger("Local Read Blocks", NULL,
    3312             :                                usage->local_blks_read, es);
    3313          72 :         ExplainPropertyInteger("Local Dirtied Blocks", NULL,
    3314             :                                usage->local_blks_dirtied, es);
    3315          72 :         ExplainPropertyInteger("Local Written Blocks", NULL,
    3316             :                                usage->local_blks_written, es);
    3317          72 :         ExplainPropertyInteger("Temp Read Blocks", NULL,
    3318             :                                usage->temp_blks_read, es);
    3319          72 :         ExplainPropertyInteger("Temp Written Blocks", NULL,
    3320             :                                usage->temp_blks_written, es);
    3321          72 :         if (track_io_timing)
    3322             :         {
    3323           0 :             ExplainPropertyFloat("I/O Read Time", "ms",
    3324           0 :                                  INSTR_TIME_GET_MILLISEC(usage->blk_read_time),
    3325             :                                  3, es);
    3326           0 :             ExplainPropertyFloat("I/O Write Time", "ms",
    3327           0 :                                  INSTR_TIME_GET_MILLISEC(usage->blk_write_time),
    3328             :                                  3, es);
    3329             :         }
    3330             :     }
    3331          80 : }
    3332             : 
    3333             : /*
    3334             :  * Show WAL usage details.
    3335             :  */
    3336             : static void
    3337           0 : show_wal_usage(ExplainState *es, const WalUsage *usage)
    3338             : {
    3339           0 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    3340             :     {
    3341             :         /* Show only positive counter values. */
    3342           0 :         if ((usage->wal_records > 0) || (usage->wal_fpi > 0) ||
    3343           0 :             (usage->wal_bytes > 0))
    3344             :         {
    3345           0 :             ExplainIndentText(es);
    3346           0 :             appendStringInfoString(es->str, "WAL:");
    3347             : 
    3348           0 :             if (usage->wal_records > 0)
    3349           0 :                 appendStringInfo(es->str, " records=%ld",
    3350             :                                  usage->wal_records);
    3351           0 :             if (usage->wal_fpi > 0)
    3352           0 :                 appendStringInfo(es->str, " fpi=%ld",
    3353             :                                  usage->wal_fpi);
    3354           0 :             if (usage->wal_bytes > 0)
    3355           0 :                 appendStringInfo(es->str, " bytes=" UINT64_FORMAT,
    3356             :                                  usage->wal_bytes);
    3357           0 :             appendStringInfoChar(es->str, '\n');
    3358             :         }
    3359             :     }
    3360             :     else
    3361             :     {
    3362           0 :         ExplainPropertyInteger("WAL Records", NULL,
    3363             :                                usage->wal_records, es);
    3364           0 :         ExplainPropertyInteger("WAL FPI", NULL,
    3365             :                                usage->wal_fpi, es);
    3366           0 :         ExplainPropertyUInteger("WAL Bytes", NULL,
    3367             :                                 usage->wal_bytes, es);
    3368             :     }
    3369           0 : }
    3370             : 
    3371             : /*
    3372             :  * Add some additional details about an IndexScan or IndexOnlyScan
    3373             :  */
    3374             : static void
    3375        3220 : ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
    3376             :                         ExplainState *es)
    3377             : {
    3378        3220 :     const char *indexname = explain_get_index_name(indexid);
    3379             : 
    3380        3220 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    3381             :     {
    3382        3220 :         if (ScanDirectionIsBackward(indexorderdir))
    3383         120 :             appendStringInfoString(es->str, " Backward");
    3384        3220 :         appendStringInfo(es->str, " using %s", indexname);
    3385             :     }
    3386             :     else
    3387             :     {
    3388             :         const char *scandir;
    3389             : 
    3390           0 :         switch (indexorderdir)
    3391             :         {
    3392           0 :             case BackwardScanDirection:
    3393           0 :                 scandir = "Backward";
    3394           0 :                 break;
    3395           0 :             case NoMovementScanDirection:
    3396           0 :                 scandir = "NoMovement";
    3397           0 :                 break;
    3398           0 :             case ForwardScanDirection:
    3399           0 :                 scandir = "Forward";
    3400           0 :                 break;
    3401           0 :             default:
    3402           0 :                 scandir = "???";
    3403           0 :                 break;
    3404             :         }
    3405           0 :         ExplainPropertyText("Scan Direction", scandir, es);
    3406           0 :         ExplainPropertyText("Index Name", indexname, es);
    3407             :     }
    3408        3220 : }
    3409             : 
    3410             : /*
    3411             :  * Show the target of a Scan node
    3412             :  */
    3413             : static void
    3414       18480 : ExplainScanTarget(Scan *plan, ExplainState *es)
    3415             : {
    3416       18480 :     ExplainTargetRel((Plan *) plan, plan->scanrelid, es);
    3417       18480 : }
    3418             : 
    3419             : /*
    3420             :  * Show the target of a ModifyTable node
    3421             :  *
    3422             :  * Here we show the nominal target (ie, the relation that was named in the
    3423             :  * original query).  If the actual target(s) is/are different, we'll show them
    3424             :  * in show_modifytable_info().
    3425             :  */
    3426             : static void
    3427         464 : ExplainModifyTarget(ModifyTable *plan, ExplainState *es)
    3428             : {
    3429         464 :     ExplainTargetRel((Plan *) plan, plan->nominalRelation, es);
    3430         464 : }
    3431             : 
    3432             : /*
    3433             :  * Show the target relation of a scan or modify node
    3434             :  */
    3435             : static void
    3436       19242 : ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
    3437             : {
    3438       19242 :     char       *objectname = NULL;
    3439       19242 :     char       *namespace = NULL;
    3440       19242 :     const char *objecttag = NULL;
    3441             :     RangeTblEntry *rte;
    3442             :     char       *refname;
    3443             : 
    3444       19242 :     rte = rt_fetch(rti, es->rtable);
    3445       19242 :     refname = (char *) list_nth(es->rtable_names, rti - 1);
    3446       19242 :     if (refname == NULL)
    3447           8 :         refname = rte->eref->aliasname;
    3448             : 
    3449       19242 :     switch (nodeTag(plan))
    3450             :     {
    3451       18522 :         case T_SeqScan:
    3452             :         case T_SampleScan:
    3453             :         case T_IndexScan:
    3454             :         case T_IndexOnlyScan:
    3455             :         case T_BitmapHeapScan:
    3456             :         case T_TidScan:
    3457             :         case T_ForeignScan:
    3458             :         case T_CustomScan:
    3459             :         case T_ModifyTable:
    3460             :             /* Assert it's on a real relation */
    3461             :             Assert(rte->rtekind == RTE_RELATION);
    3462       18522 :             objectname = get_rel_name(rte->relid);
    3463       18522 :             if (es->verbose)
    3464        1554 :                 namespace = get_namespace_name(get_rel_namespace(rte->relid));
    3465       18522 :             objecttag = "Relation Name";
    3466       18522 :             break;
    3467         188 :         case T_FunctionScan:
    3468             :             {
    3469         188 :                 FunctionScan *fscan = (FunctionScan *) plan;
    3470             : 
    3471             :                 /* Assert it's on a RangeFunction */
    3472             :                 Assert(rte->rtekind == RTE_FUNCTION);
    3473             : 
    3474             :                 /*
    3475             :                  * If the expression is still a function call of a single
    3476             :                  * function, we can get the real name of the function.
    3477             :                  * Otherwise, punt.  (Even if it was a single function call
    3478             :                  * originally, the optimizer could have simplified it away.)
    3479             :                  */
    3480         188 :                 if (list_length(fscan->functions) == 1)
    3481             :                 {
    3482         188 :                     RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions);
    3483             : 
    3484         188 :                     if (IsA(rtfunc->funcexpr, FuncExpr))
    3485             :                     {
    3486         172 :                         FuncExpr   *funcexpr = (FuncExpr *) rtfunc->funcexpr;
    3487         172 :                         Oid         funcid = funcexpr->funcid;
    3488             : 
    3489         172 :                         objectname = get_func_name(funcid);
    3490         172 :                         if (es->verbose)
    3491             :                             namespace =
    3492          40 :                                 get_namespace_name(get_func_namespace(funcid));
    3493             :                     }
    3494             :                 }
    3495         188 :                 objecttag = "Function Name";
    3496             :             }
    3497         188 :             break;
    3498          20 :         case T_TableFuncScan:
    3499             :             Assert(rte->rtekind == RTE_TABLEFUNC);
    3500          20 :             objectname = "xmltable";
    3501          20 :             objecttag = "Table Function Name";
    3502          20 :             break;
    3503         154 :         case T_ValuesScan:
    3504             :             Assert(rte->rtekind == RTE_VALUES);
    3505         154 :             break;
    3506         110 :         case T_CteScan:
    3507             :             /* Assert it's on a non-self-reference CTE */
    3508             :             Assert(rte->rtekind == RTE_CTE);
    3509             :             Assert(!rte->self_reference);
    3510         110 :             objectname = rte->ctename;
    3511         110 :             objecttag = "CTE Name";
    3512         110 :             break;
    3513           0 :         case T_NamedTuplestoreScan:
    3514             :             Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);
    3515           0 :             objectname = rte->enrname;
    3516           0 :             objecttag = "Tuplestore Name";
    3517           0 :             break;
    3518          12 :         case T_WorkTableScan:
    3519             :             /* Assert it's on a self-reference CTE */
    3520             :             Assert(rte->rtekind == RTE_CTE);
    3521             :             Assert(rte->self_reference);
    3522          12 :             objectname = rte->ctename;
    3523          12 :             objecttag = "CTE Name";
    3524          12 :             break;
    3525         236 :         default:
    3526         236 :             break;
    3527             :     }
    3528             : 
    3529       19242 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    3530             :     {
    3531       19002 :         appendStringInfoString(es->str, " on");
    3532       19002 :         if (namespace != NULL)
    3533        1590 :             appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
    3534             :                              quote_identifier(objectname));
    3535       17412 :         else if (objectname != NULL)
    3536       17006 :             appendStringInfo(es->str, " %s", quote_identifier(objectname));
    3537       19002 :         if (objectname == NULL || strcmp(refname, objectname) != 0)
    3538       11212 :             appendStringInfo(es->str, " %s", quote_identifier(refname));
    3539             :     }
    3540             :     else
    3541             :     {
    3542         240 :         if (objecttag != NULL && objectname != NULL)
    3543         240 :             ExplainPropertyText(objecttag, objectname, es);
    3544         240 :         if (namespace != NULL)
    3545           4 :             ExplainPropertyText("Schema", namespace, es);
    3546         240 :         ExplainPropertyText("Alias", refname, es);
    3547             :     }
    3548       19242 : }
    3549             : 
    3550             : /*
    3551             :  * Show extra information for a ModifyTable node
    3552             :  *
    3553             :  * We have three objectives here.  First, if there's more than one target
    3554             :  * table or it's different from the nominal target, identify the actual
    3555             :  * target(s).  Second, give FDWs a chance to display extra info about foreign
    3556             :  * targets.  Third, show information about ON CONFLICT.
    3557             :  */
    3558             : static void
    3559         464 : show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
    3560             :                       ExplainState *es)
    3561             : {
    3562         464 :     ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
    3563             :     const char *operation;
    3564             :     const char *foperation;
    3565             :     bool        labeltargets;
    3566             :     int         j;
    3567         464 :     List       *idxNames = NIL;
    3568             :     ListCell   *lst;
    3569             : 
    3570         464 :     switch (node->operation)
    3571             :     {
    3572         124 :         case CMD_INSERT:
    3573         124 :             operation = "Insert";
    3574         124 :             foperation = "Foreign Insert";
    3575         124 :             break;
    3576         232 :         case CMD_UPDATE:
    3577         232 :             operation = "Update";
    3578         232 :             foperation = "Foreign Update";
    3579         232 :             break;
    3580         108 :         case CMD_DELETE:
    3581         108 :             operation = "Delete";
    3582         108 :             foperation = "Foreign Delete";
    3583         108 :             break;
    3584           0 :         default:
    3585           0 :             operation = "???";
    3586           0 :             foperation = "Foreign ???";
    3587           0 :             break;
    3588             :     }
    3589             : 
    3590             :     /* Should we explicitly label target relations? */
    3591         830 :     labeltargets = (mtstate->mt_nplans > 1 ||
    3592         366 :                     (mtstate->mt_nplans == 1 &&
    3593         366 :                      mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
    3594             : 
    3595         464 :     if (labeltargets)
    3596         128 :         ExplainOpenGroup("Target Tables", "Target Tables", false, es);
    3597             : 
    3598        1098 :     for (j = 0; j < mtstate->mt_nplans; j++)
    3599             :     {
    3600         634 :         ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
    3601         634 :         FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
    3602             : 
    3603         634 :         if (labeltargets)
    3604             :         {
    3605             :             /* Open a group for this target */
    3606         298 :             ExplainOpenGroup("Target Table", NULL, true, es);
    3607             : 
    3608             :             /*
    3609             :              * In text mode, decorate each target with operation type, so that
    3610             :              * ExplainTargetRel's output of " on foo" will read nicely.
    3611             :              */
    3612         298 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    3613             :             {
    3614         298 :                 ExplainIndentText(es);
    3615         298 :                 appendStringInfoString(es->str,
    3616             :                                        fdwroutine ? foperation : operation);
    3617             :             }
    3618             : 
    3619             :             /* Identify target */
    3620         298 :             ExplainTargetRel((Plan *) node,
    3621             :                              resultRelInfo->ri_RangeTableIndex,
    3622             :                              es);
    3623             : 
    3624         298 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    3625             :             {
    3626         298 :                 appendStringInfoChar(es->str, '\n');
    3627         298 :                 es->indent++;
    3628             :             }
    3629             :         }
    3630             : 
    3631             :         /* Give FDW a chance if needed */
    3632         634 :         if (!resultRelInfo->ri_usesFdwDirectModify &&
    3633          50 :             fdwroutine != NULL &&
    3634          50 :             fdwroutine->ExplainForeignModify != NULL)
    3635             :         {
    3636          50 :             List       *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
    3637             : 
    3638          50 :             fdwroutine->ExplainForeignModify(mtstate,
    3639             :                                              resultRelInfo,
    3640             :                                              fdw_private,
    3641             :                                              j,
    3642             :                                              es);
    3643             :         }
    3644             : 
    3645         634 :         if (labeltargets)
    3646             :         {
    3647             :             /* Undo the indentation we added in text format */
    3648         298 :             if (es->format == EXPLAIN_FORMAT_TEXT)
    3649         298 :                 es->indent--;
    3650             : 
    3651             :             /* Close the group */
    3652         298 :             ExplainCloseGroup("Target Table", NULL, true, es);
    3653             :         }
    3654             :     }
    3655             : 
    3656             :     /* Gather names of ON CONFLICT arbiter indexes */
    3657         588 :     foreach(lst, node->arbiterIndexes)
    3658             :     {
    3659         124 :         char       *indexname = get_rel_name(lfirst_oid(lst));
    3660             : 
    3661         124 :         idxNames = lappend(idxNames, indexname);
    3662             :     }
    3663             : 
    3664         464 :     if (node->onConflictAction != ONCONFLICT_NONE)
    3665             :     {
    3666          88 :         ExplainPropertyText("Conflict Resolution",
    3667          88 :                             node->onConflictAction == ONCONFLICT_NOTHING ?
    3668             :                             "NOTHING" : "UPDATE",
    3669             :                             es);
    3670             : 
    3671             :         /*
    3672             :          * Don't display arbiter indexes at all when DO NOTHING variant
    3673             :          * implicitly ignores all conflicts
    3674             :          */
    3675          88 :         if (idxNames)
    3676          88 :             ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es);
    3677             : 
    3678             :         /* ON CONFLICT DO UPDATE WHERE qual is specially displayed */
    3679          88 :         if (node->onConflictWhere)
    3680             :         {
    3681          36 :             show_upper_qual((List *) node->onConflictWhere, "Conflict Filter",
    3682             :                             &mtstate->ps, ancestors, es);
    3683          36 :             show_instrumentation_count("Rows Removed by Conflict Filter", 1, &mtstate->ps, es);
    3684             :         }
    3685             : 
    3686             :         /* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */
    3687          88 :         if (es->analyze && mtstate->ps.instrument)
    3688             :         {
    3689             :             double      total;
    3690             :             double      insert_path;
    3691             :             double      other_path;
    3692             : 
    3693           0 :             InstrEndLoop(mtstate->mt_plans[0]->instrument);
    3694             : 
    3695             :             /* count the number of source rows */
    3696           0 :             total = mtstate->mt_plans[0]->instrument->ntuples;
    3697           0 :             other_path = mtstate->ps.instrument->ntuples2;
    3698           0 :             insert_path = total - other_path;
    3699             : 
    3700           0 :             ExplainPropertyFloat("Tuples Inserted", NULL,
    3701             :                                  insert_path, 0, es);
    3702           0 :             ExplainPropertyFloat("Conflicting Tuples", NULL,
    3703             :                                  other_path, 0, es);
    3704             :         }
    3705             :     }
    3706             : 
    3707         464 :     if (labeltargets)
    3708         128 :         ExplainCloseGroup("Target Tables", "Target Tables", false, es);
    3709         464 : }
    3710             : 
    3711             : /*
    3712             :  * Explain the constituent plans of a ModifyTable, Append, MergeAppend,
    3713             :  * BitmapAnd, or BitmapOr node.
    3714             :  *
    3715             :  * The ancestors list should already contain the immediate parent of these
    3716             :  * plans.
    3717             :  */
    3718             : static void
    3719        2528 : ExplainMemberNodes(PlanState **planstates, int nplans,
    3720             :                    List *ancestors, ExplainState *es)
    3721             : {
    3722             :     int         j;
    3723             : 
    3724        9822 :     for (j = 0; j < nplans; j++)
    3725        7294 :         ExplainNode(planstates[j], ancestors,
    3726             :                     "Member", NULL, es);
    3727        2528 : }
    3728             : 
    3729             : /*
    3730             :  * Report about any pruned subnodes of an Append or MergeAppend node.
    3731             :  *
    3732             :  * nplans indicates the number of live subplans.
    3733             :  * nchildren indicates the original number of subnodes in the Plan;
    3734             :  * some of these may have been pruned by the run-time pruning code.
    3735             :  */
    3736             : static void
    3737        1992 : ExplainMissingMembers(int nplans, int nchildren, ExplainState *es)
    3738             : {
    3739        1992 :     if (nplans < nchildren || es->format != EXPLAIN_FORMAT_TEXT)
    3740         116 :         ExplainPropertyInteger("Subplans Removed", NULL,
    3741         116 :                                nchildren - nplans, es);
    3742        1992 : }
    3743             : 
    3744             : /*
    3745             :  * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).
    3746             :  *
    3747             :  * The ancestors list should already contain the immediate parent of these
    3748             :  * SubPlans.
    3749             :  */
    3750             : static void
    3751         778 : ExplainSubPlans(List *plans, List *ancestors,
    3752             :                 const char *relationship, ExplainState *es)
    3753             : {
    3754             :     ListCell   *lst;
    3755             : 
    3756        1688 :     foreach(lst, plans)
    3757             :     {
    3758         910 :         SubPlanState *sps = (SubPlanState *) lfirst(lst);
    3759         910 :         SubPlan    *sp = sps->subplan;
    3760             : 
    3761             :         /*
    3762             :          * There can be multiple SubPlan nodes referencing the same physical
    3763             :          * subplan (same plan_id, which is its index in PlannedStmt.subplans).
    3764             :          * We should print a subplan only once, so track which ones we already
    3765             :          * printed.  This state must be global across the plan tree, since the
    3766             :          * duplicate nodes could be in different plan nodes, eg both a bitmap
    3767             :          * indexscan's indexqual and its parent heapscan's recheck qual.  (We
    3768             :          * do not worry too much about which plan node we show the subplan as
    3769             :          * attached to in such cases.)
    3770             :          */
    3771         910 :         if (bms_is_member(sp->plan_id, es->printed_subplans))
    3772          72 :             continue;
    3773         838 :         es->printed_subplans = bms_add_member(es->printed_subplans,
    3774             :                                               sp->plan_id);
    3775             : 
    3776             :         /*
    3777             :          * Treat the SubPlan node as an ancestor of the plan node(s) within
    3778             :          * it, so that ruleutils.c can find the referents of subplan
    3779             :          * parameters.
    3780             :          */
    3781         838 :         ancestors = lcons(sp, ancestors);
    3782             : 
    3783         838 :         ExplainNode(sps->planstate, ancestors,
    3784         838 :                     relationship, sp->plan_name, es);
    3785             : 
    3786         838 :         ancestors = list_delete_first(ancestors);
    3787             :     }
    3788         778 : }
    3789             : 
    3790             : /*
    3791             :  * Explain a list of children of a CustomScan.
    3792             :  */
    3793             : static void
    3794           0 : ExplainCustomChildren(CustomScanState *css, List *ancestors, ExplainState *es)
    3795             : {
    3796             :     ListCell   *cell;
    3797           0 :     const char *label =
    3798           0 :     (list_length(css->custom_ps) != 1 ? "children" : "child");
    3799             : 
    3800           0 :     foreach(cell, css->custom_ps)
    3801           0 :         ExplainNode((PlanState *) lfirst(cell), ancestors, label, NULL, es);
    3802           0 : }
    3803             : 
    3804             : /*
    3805             :  * Create a per-plan-node workspace for collecting per-worker data.
    3806             :  *
    3807             :  * Output related to each worker will be temporarily "set aside" into a
    3808             :  * separate buffer, which we'll merge into the main output stream once
    3809             :  * we've processed all data for the plan node.  This makes it feasible to
    3810             :  * generate a coherent sub-group of fields for each worker, even though the
    3811             :  * code that produces the fields is in several different places in this file.
    3812             :  * Formatting of such a set-aside field group is managed by
    3813             :  * ExplainOpenSetAsideGroup and ExplainSaveGroup/ExplainRestoreGroup.
    3814             :  */
    3815             : static ExplainWorkersState *
    3816         640 : ExplainCreateWorkersState(int num_workers)
    3817             : {
    3818             :     ExplainWorkersState *wstate;
    3819             : 
    3820         640 :     wstate = (ExplainWorkersState *) palloc(sizeof(ExplainWorkersState));
    3821         640 :     wstate->num_workers = num_workers;
    3822         640 :     wstate->worker_inited = (bool *) palloc0(num_workers * sizeof(bool));
    3823         640 :     wstate->worker_str = (StringInfoData *)
    3824         640 :         palloc0(num_workers * sizeof(StringInfoData));
    3825         640 :     wstate->worker_state_save = (int *) palloc(num_workers * sizeof(int));
    3826         640 :     return wstate;
    3827             : }
    3828             : 
    3829             : /*
    3830             :  * Begin or resume output into the set-aside group for worker N.
    3831             :  */
    3832             : static void
    3833          96 : ExplainOpenWorker(int n, ExplainState *es)
    3834             : {
    3835          96 :     ExplainWorkersState *wstate = es->workers_state;
    3836             : 
    3837             :     Assert(wstate);
    3838             :     Assert(n >= 0 && n < wstate->num_workers);
    3839             : 
    3840             :     /* Save prior output buffer pointer */
    3841          96 :     wstate->prev_str = es->str;
    3842             : 
    3843          96 :     if (!wstate->worker_inited[n])
    3844             :     {
    3845             :         /* First time through, so create the buffer for this worker */
    3846          48 :         initStringInfo(&wstate->worker_str[n]);
    3847          48 :         es->str = &wstate->worker_str[n];
    3848             : 
    3849             :         /*
    3850             :          * Push suitable initial formatting state for this worker's field
    3851             :          * group.  We allow one extra logical nesting level, since this group
    3852             :          * will eventually be wrapped in an outer "Workers" group.
    3853             :          */
    3854          48 :         ExplainOpenSetAsideGroup("Worker", NULL, true, 2, es);
    3855             : 
    3856             :         /*
    3857             :          * In non-TEXT formats we always emit a "Worker Number" field, even if
    3858             :          * there's no other data for this worker.
    3859             :          */
    3860          48 :         if (es->format != EXPLAIN_FORMAT_TEXT)
    3861          32 :             ExplainPropertyInteger("Worker Number", NULL, n, es);
    3862             : 
    3863          48 :         wstate->worker_inited[n] = true;
    3864             :     }
    3865             :     else
    3866             :     {
    3867             :         /* Resuming output for a worker we've already emitted some data for */
    3868          48 :         es->str = &wstate->worker_str[n];
    3869             : 
    3870             :         /* Restore formatting state saved by last ExplainCloseWorker() */
    3871          48 :         ExplainRestoreGroup(es, 2, &wstate->worker_state_save[n]);
    3872             :     }
    3873             : 
    3874             :     /*
    3875             :      * In TEXT format, prefix the first output line for this worker with
    3876             :      * "Worker N:".  Then, any additional lines should be indented one more
    3877             :      * stop than the "Worker N" line is.
    3878             :      */
    3879          96 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    3880             :     {
    3881          16 :         if (es->str->len == 0)
    3882             :         {
    3883          16 :             ExplainIndentText(es);
    3884          16 :             appendStringInfo(es->str, "Worker %d:  ", n);
    3885             :         }
    3886             : 
    3887          16 :         es->indent++;
    3888             :     }
    3889          96 : }
    3890             : 
    3891             : /*
    3892             :  * End output for worker N --- must pair with previous ExplainOpenWorker call
    3893             :  */
    3894             : static void
    3895          96 : ExplainCloseWorker(int n, ExplainState *es)
    3896             : {
    3897          96 :     ExplainWorkersState *wstate = es->workers_state;
    3898             : 
    3899             :     Assert(wstate);
    3900             :     Assert(n >= 0 && n < wstate->num_workers);
    3901             :     Assert(wstate->worker_inited[n]);
    3902             : 
    3903             :     /*
    3904             :      * Save formatting state in case we do another ExplainOpenWorker(), then
    3905             :      * pop the formatting stack.
    3906             :      */
    3907          96 :     ExplainSaveGroup(es, 2, &wstate->worker_state_save[n]);
    3908             : 
    3909             :     /*
    3910             :      * In TEXT format, if we didn't actually produce any output line(s) then
    3911             :      * truncate off the partial line emitted by ExplainOpenWorker.  (This is
    3912             :      * to avoid bogus output if, say, show_buffer_usage chooses not to print
    3913             :      * anything for the worker.)  Also fix up the indent level.
    3914             :      */
    3915          96 :     if (es->format == EXPLAIN_FORMAT_TEXT)
    3916             :     {
    3917          16 :         while (es->str->len > 0 && es->str->data[es->str->len - 1] != '\n')
    3918           0 :             es->str->data[--(es->str->len)] = '\0';
    3919             : 
    3920          16 :         es->indent--;
    3921             :     }
    3922             : 
    3923             :     /* Restore prior output buffer pointer */
    3924          96 :     es->str = wstate->prev_str;
    3925          96 : }
    3926             : 
    3927             : /*
    3928             :  * Print per-worker info for current node, then free the ExplainWorkersState.
    3929             :  */
    3930             : static void
    3931         640 : ExplainFlushWorkersState(ExplainState *es)
    3932             : {
    3933         640 :     ExplainWorkersState *wstate = es->workers_state;
    3934             : 
    3935         640 :     ExplainOpenGroup("Workers", "Workers", false, es);
    3936        1668 :     for (int i = 0; i < wstate->num_workers; i++)
    3937             :     {
    3938        1028 :         if (wstate->worker_inited[i])
    3939             :         {
    3940             :             /* This must match previous ExplainOpenSetAsideGroup call */
    3941          48 :             ExplainOpenGroup("Worker", NULL, true, es);
    3942          48 :             appendStringInfoString(es->str, wstate->worker_str[i].data);
    3943          48 :             ExplainCloseGroup("Worker", NULL, true, es);
    3944             : 
    3945          48 :             pfree(wstate->worker_str[i].data);
    3946             :         }
    3947             :     }
    3948         640 :     ExplainCloseGroup("Workers", "Workers", false, es);
    3949             : 
    3950         640 :     pfree(wstate->worker_inited);
    3951         640 :     pfree(wstate->worker_str);
    3952         640 :     pfree(wstate->worker_state_save);
    3953         640 :     pfree(wstate);
    3954         640 : }
    3955             : 
    3956             : /*
    3957             :  * Explain a property, such as sort keys or targets, that takes the form of
    3958             :  * a list of unlabeled items.  "data" is a list of C strings.
    3959             :  */
    3960             : void
    3961        7338 : ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
    3962             : {
    3963             :     ListCell   *lc;
    3964        7338 :     bool        first = true;
    3965             : 
    3966        7338 :     switch (es->format)
    3967             :     {
    3968        7246 :         case EXPLAIN_FORMAT_TEXT:
    3969        7246 :             ExplainIndentText(es);
    3970        7246 :             appendStringInfo(es->str, "%s: ", qlabel);
    3971       21662 :             foreach(lc, data)
    3972             :             {
    3973       14416 :                 if (!first)
    3974        7170 :                     appendStringInfoString(es->str, ", ");
    3975       14416 :                 appendStringInfoString(es->str, (const char *) lfirst(lc));
    3976       14416 :                 first = false;
    3977             :             }
    3978        7246 :             appendStringInfoChar(es->str, '\n');
    3979        7246 :             break;
    3980             : 
    3981           0 :         case EXPLAIN_FORMAT_XML:
    3982           0 :             ExplainXMLTag(qlabel, X_OPENING, es);
    3983           0 :             foreach(lc, data)
    3984             :             {
    3985             :                 char       *str;
    3986             : 
    3987           0 :                 appendStringInfoSpaces(es->str, es->indent * 2 + 2);
    3988           0 :                 appendStringInfoString(es->str, "<Item>");
    3989           0 :                 str = escape_xml((const char *) lfirst(lc));
    3990           0 :                 appendStringInfoString(es->str, str);
    3991           0 :                 pfree(str);
    3992           0 :                 appendStringInfoString(es->str, "</Item>\n");
    3993             :             }
    3994           0 :             ExplainXMLTag(qlabel, X_CLOSING, es);
    3995           0 :             break;
    3996             : 
    3997          92 :         case EXPLAIN_FORMAT_JSON:
    3998          92 :             ExplainJSONLineEnding(es);
    3999          92 :             appendStringInfoSpaces(es->str, es->indent * 2);
    4000          92 :             escape_json(es->str, qlabel);
    4001          92 :             appendStringInfoString(es->str, ": [");
    4002         396 :             foreach(lc, data)
    4003             :             {
    4004         304 :                 if (!first)
    4005         212 :                     appendStringInfoString(es->str, ", ");
    4006         304 :                 escape_json(es->str, (const char *) lfirst(lc));
    4007         304 :                 first = false;
    4008             :             }
    4009          92 :             appendStringInfoChar(es->str, ']');
    4010          92 :             break;
    4011             : 
    4012           0 :         case EXPLAIN_FORMAT_YAML:
    4013           0 :             ExplainYAMLLineStarting(es);
    4014           0 :             appendStringInfo(es->str, "%s: ", qlabel);
    4015           0 :             foreach(lc, data)
    4016             :             {
    4017           0 :                 appendStringInfoChar(es->str, '\n');
    4018           0 :                 appendStringInfoSpaces(es->str, es->indent * 2 + 2);
    4019           0 :                 appendStringInfoString(es->str, "- ");
    4020           0 :                 escape_yaml(es->str, (const char *) lfirst(lc));
    4021             :             }
    4022           0 :             break;
    4023             :     }
    4024        7338 : }
    4025             : 
    4026             : /*
    4027             :  * Explain a property that takes the form of a list of unlabeled items within
    4028             :  * another list.  "data" is a list of C strings.
    4029             :  */
    4030             : void
    4031         292 : ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
    4032             : {
    4033             :     ListCell   *lc;
    4034         292 :     bool        first = true;
    4035             : 
    4036         292 :     switch (es->format)
    4037             :     {
    4038         292 :         case EXPLAIN_FORMAT_TEXT:
    4039             :         case EXPLAIN_FORMAT_XML:
    4040         292 :             ExplainPropertyList(qlabel, data, es);
    4041         292 :             return;
    4042             : 
    4043           0 :         case EXPLAIN_FORMAT_JSON:
    4044           0 :             ExplainJSONLineEnding(es);
    4045           0 :             appendStringInfoSpaces(es->str, es->indent * 2);
    4046           0 :             appendStringInfoChar(es->str, '[');
    4047           0 :             foreach(lc, data)
    4048             :             {
    4049           0 :                 if (!first)
    4050           0 :                     appendStringInfoString(es->str, ", ");
    4051           0 :                 escape_json(es->str, (const char *) lfirst(lc));
    4052           0 :                 first = false;
    4053             :             }
    4054           0 :             appendStringInfoChar(es->str, ']');
    4055           0 :             break;
    4056             : 
    4057           0 :         case EXPLAIN_FORMAT_YAML:
    4058           0 :             ExplainYAMLLineStarting(es);
    4059           0 :             appendStringInfoString(es->str, "- [");
    4060           0 :             foreach(lc, data)
    4061             :             {
    4062           0 :                 if (!first)
    4063           0 :                     appendStringInfoString(es->str, ", ");
    4064           0 :                 escape_yaml(es->str, (const char *) lfirst(lc));
    4065           0 :                 first = false;
    4066             :             }
    4067           0 :             appendStringInfoChar(es->str, ']');
    4068           0 :             break;
    4069             :     }
    4070             : }
    4071             : 
    4072             : /*
    4073             :  * Explain a simple property.
    4074             :  *
    4075             :  * If "numeric" is true, the value is a number (or other value that
    4076             :  * doesn't need quoting in JSON).
    4077             :  *
    4078             :  * If unit is non-NULL the text format will display it after the value.
    4079             :  *
    4080             :  * This usually should not be invoked directly, but via one of the datatype
    4081             :  * specific routines ExplainPropertyText, ExplainPropertyInteger, etc.
    4082             :  */
    4083             : static void
    4084       30340 : ExplainProperty(const char *qlabel, const char *unit, const char *value,
    4085             :                 bool numeric, ExplainState *es)
    4086             : {
    4087       30340 :     switch (es->format)
    4088             :     {
    4089       20404 :         case EXPLAIN_FORMAT_TEXT:
    4090       20404 :             ExplainIndentText(es);
    4091       20404 :             if (unit)
    4092        1508 :                 appendStringInfo(es->str, "%s: %s %s\n", qlabel, value, unit);
    4093             :             else
    4094       18896 :                 appendStringInfo(es->str, "%s: %s\n", qlabel, value);
    4095       20404 :             break;
    4096             : 
    4097         136 :         case EXPLAIN_FORMAT_XML:
    4098             :             {
    4099             :                 char       *str;
    4100             : 
    4101         136 :                 appendStringInfoSpaces(es->str, es->indent * 2);
    4102         136 :                 ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es);
    4103         136 :                 str = escape_xml(value);
    4104         136 :                 appendStringInfoString(es->str, str);
    4105         136 :                 pfree(str);
    4106         136 :                 ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);
    4107         136 :                 appendStringInfoChar(es->str, '\n');
    4108             :             }
    4109         136 :             break;
    4110             : 
    4111        9664 :         case EXPLAIN_FORMAT_JSON:
    4112        9664 :             ExplainJSONLineEnding(es);
    4113        9664 :             appendStringInfoSpaces(es->str, es->indent * 2);
    4114        9664 :             escape_json(es->str, qlabel);
    4115        9664 :             appendStringInfoString(es->str, ": ");
    4116        9664 :             if (numeric)
    4117        7428 :                 appendStringInfoString(es->str, value);
    4118             :             else
    4119        2236 :                 escape_json(es->str, value);
    4120        9664 :             break;
    4121             : 
    4122         136 :         case EXPLAIN_FORMAT_YAML:
    4123         136 :             ExplainYAMLLineStarting(es);
    4124         136 :             appendStringInfo(es->str, "%s: ", qlabel);
    4125         136 :             if (numeric)
    4126         124 :                 appendStringInfoString(es->str, value);
    4127             :             else
    4128          12 :                 escape_yaml(es->str, value);
    4129         136 :             break;
    4130             :     }
    4131       30340 : }
    4132             : 
    4133             : /*
    4134             :  * Explain a string-valued property.
    4135             :  */
    4136             : void
    4137       20078 : ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
    4138             : {
    4139       20078 :     ExplainProperty(qlabel, NULL, value, false, es);
    4140       20078 : }
    4141             : 
    4142             : /*
    4143             :  * Explain an integer-valued property.
    4144             :  */
    4145             : void
    4146        2644 : ExplainPropertyInteger(const char *qlabel, const char *unit, int64 value,
    4147             :                        ExplainState *es)
    4148             : {
    4149             :     char        buf[32];
    4150             : 
    4151        2644 :     snprintf(buf, sizeof(buf), INT64_FORMAT, value);
    4152        2644 :     ExplainProperty(qlabel, unit, buf, true, es);
    4153        2644 : }
    4154             : 
    4155             : /*
    4156             :  * Explain an unsigned integer-valued property.
    4157             :  */
    4158             : void
    4159           0 : ExplainPropertyUInteger(const char *qlabel, const char *unit, uint64 value,
    4160             :                         ExplainState *es)
    4161             : {
    4162             :     char        buf[32];
    4163             : 
    4164           0 :     snprintf(buf, sizeof(buf), UINT64_FORMAT, value);
    4165           0 :     ExplainProperty(qlabel, unit, buf, true, es);
    4166           0 : }
    4167             : 
    4168             : /*
    4169             :  * Explain a float-valued property, using the specified number of
    4170             :  * fractional digits.
    4171             :  */
    4172             : void
    4173        6680 : ExplainPropertyFloat(const char *qlabel, const char *unit, double value,
    4174             :                      int ndigits, ExplainState *es)
    4175             : {
    4176             :     char       *buf;
    4177             : 
    4178        6680 :     buf = psprintf("%.*f", ndigits, value);
    4179        6680 :     ExplainProperty(qlabel, unit, buf, true, es);
    4180        6680 :     pfree(buf);
    4181        6680 : }
    4182             : 
    4183             : /*
    4184             :  * Explain a bool-valued property.
    4185             :  */
    4186             : void
    4187         938 : ExplainPropertyBool(const char *qlabel, bool value, ExplainState *es)
    4188             : {
    4189         938 :     ExplainProperty(qlabel, NULL, value ? "true" : "false", true, es);
    4190         938 : }
    4191             : 
    4192             : /*
    4193             :  * Open a group of related objects.
    4194             :  *
    4195             :  * objtype is the type of the group object, labelname is its label within
    4196             :  * a containing object (if any).
    4197             :  *
    4198             :  * If labeled is true, the group members will be labeled properties,
    4199             :  * while if it's false, they'll be unlabeled objects.
    4200             :  */
    4201             : void
    4202       71170 : ExplainOpenGroup(const char *objtype, const char *labelname,
    4203             :                  bool labeled, ExplainState *es)
    4204             : {
    4205       71170 :     switch (es->format)
    4206             :     {
    4207       69258 :         case EXPLAIN_FORMAT_TEXT:
    4208             :             /* nothing to do */
    4209       69258 :             break;
    4210             : 
    4211          16 :         case EXPLAIN_FORMAT_XML:
    4212          16 :             ExplainXMLTag(objtype, X_OPENING, es);
    4213          16 :             es->indent++;
    4214          16 :             break;
    4215             : 
    4216        1880 :         case EXPLAIN_FORMAT_JSON:
    4217        1880 :             ExplainJSONLineEnding(es);
    4218        1880 :             appendStringInfoSpaces(es->str, 2 * es->indent);
    4219        1880 :             if (labelname)
    4220             :             {
    4221        1192 :                 escape_json(es->str, labelname);
    4222        1192 :                 appendStringInfoString(es->str, ": ");
    4223             :             }
    4224        1880 :             appendStringInfoChar(es->str, labeled ? '{' : '[');
    4225             : 
    4226             :             /*
    4227             :              * In JSON format, the grouping_stack is an integer list.  0 means
    4228             :              * we've emitted nothing at this grouping level, 1 means we've
    4229             :              * emitted something (and so the next item needs a comma). See
    4230             :              * ExplainJSONLineEnding().
    4231             :              */
    4232        1880 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
    4233        1880 :             es->indent++;
    4234        1880 :             break;
    4235             : 
    4236          16 :         case EXPLAIN_FORMAT_YAML:
    4237             : 
    4238             :             /*
    4239             :              * In YAML format, the grouping stack is an integer list.  0 means
    4240             :              * we've emitted nothing at this grouping level AND this grouping
    4241             :              * level is unlabelled and must be marked with "- ".  See
    4242             :              * ExplainYAMLLineStarting().
    4243             :              */
    4244          16 :             ExplainYAMLLineStarting(es);
    4245          16 :             if (labelname)
    4246             :             {
    4247          12 :                 appendStringInfo(es->str, "%s: ", labelname);
    4248          12 :                 es->grouping_stack = lcons_int(1, es->grouping_stack);
    4249             :             }
    4250             :             else
    4251             :             {
    4252           4 :                 appendStringInfoString(es->str, "- ");
    4253           4 :                 es->grouping_stack = lcons_int(0, es->grouping_stack);
    4254             :             }
    4255          16 :             es->indent++;
    4256          16 :             break;
    4257             :     }
    4258       71170 : }
    4259             : 
    4260             : /*
    4261             :  * Close a group of related objects.
    4262             :  * Parameters must match the corresponding ExplainOpenGroup call.
    4263             :  */
    4264             : void
    4265       71170 : ExplainCloseGroup(const char *objtype, const char *labelname,
    4266             :                   bool labeled, ExplainState *es)
    4267             : {
    4268       71170 :     switch (es->format)
    4269             :     {
    4270       69258 :         case EXPLAIN_FORMAT_TEXT:
    4271             :             /* nothing to do */
    4272       69258 :             break;
    4273             : 
    4274          16 :         case EXPLAIN_FORMAT_XML:
    4275          16 :             es->indent--;
    4276          16 :             ExplainXMLTag(objtype, X_CLOSING, es);
    4277          16 :             break;
    4278             : 
    4279        1880 :         case EXPLAIN_FORMAT_JSON:
    4280        1880 :             es->indent--;
    4281        1880 :             appendStringInfoChar(es->str, '\n');
    4282        1880 :             appendStringInfoSpaces(es->str, 2 * es->indent);
    4283        1880 :             appendStringInfoChar(es->str, labeled ? '}' : ']');
    4284        1880 :             es->grouping_stack = list_delete_first(es->grouping_stack);
    4285        1880 :             break;
    4286             : 
    4287          16 :         case EXPLAIN_FORMAT_YAML:
    4288          16 :             es->indent--;
    4289          16 :             es->grouping_stack = list_delete_first(es->grouping_stack);
    4290          16 :             break;
    4291             :     }
    4292       71170 : }
    4293             : 
    4294             : /*
    4295             :  * Open a group of related objects, without emitting actual data.
    4296             :  *
    4297             :  * Prepare the formatting state as though we were beginning a group with
    4298             :  * the identified properties, but don't actually emit anything.  Output
    4299             :  * subsequent to this call can be redirected into a separate output buffer,
    4300             :  * and then eventually appended to the main output buffer after doing a
    4301             :  * regular ExplainOpenGroup call (with the same parameters).
    4302             :  *
    4303             :  * The extra "depth" parameter is the new group's depth compared to current.
    4304             :  * It could be more than one, in case the eventual output will be enclosed
    4305             :  * in additional nesting group levels.  We assume we don't need to track
    4306             :  * formatting state for those levels while preparing this group's output.
    4307             :  *
    4308             :  * There is no ExplainCloseSetAsideGroup --- in current usage, we always
    4309             :  * pop this state with ExplainSaveGroup.
    4310             :  */
    4311             : static void
    4312          48 : ExplainOpenSetAsideGroup(const char *objtype, const char *labelname,
    4313             :                          bool labeled, int depth, ExplainState *es)
    4314             : {
    4315          48 :     switch (es->format)
    4316             :     {
    4317          16 :         case EXPLAIN_FORMAT_TEXT:
    4318             :             /* nothing to do */
    4319          16 :             break;
    4320             : 
    4321           0 :         case EXPLAIN_FORMAT_XML:
    4322           0 :             es->indent += depth;
    4323           0 :             break;
    4324             : 
    4325          32 :         case EXPLAIN_FORMAT_JSON:
    4326          32 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
    4327          32 :             es->indent += depth;
    4328          32 :             break;
    4329             : 
    4330           0 :         case EXPLAIN_FORMAT_YAML:
    4331           0 :             if (labelname)
    4332           0 :                 es->grouping_stack = lcons_int(1, es->grouping_stack);
    4333             :             else
    4334           0 :                 es->grouping_stack = lcons_int(0, es->grouping_stack);
    4335           0 :             es->indent += depth;
    4336           0 :             break;
    4337             :     }
    4338          48 : }
    4339             : 
    4340             : /*
    4341             :  * Pop one level of grouping state, allowing for a re-push later.
    4342             :  *
    4343             :  * This is typically used after ExplainOpenSetAsideGroup; pass the
    4344             :  * same "depth" used for that.
    4345             :  *
    4346             :  * This should not emit any output.  If state needs to be saved,
    4347             :  * save it at *state_save.  Currently, an integer save area is sufficient
    4348             :  * for all formats, but we might need to revisit that someday.
    4349             :  */
    4350             : static void
    4351          96 : ExplainSaveGroup(ExplainState *es, int depth, int *state_save)
    4352             : {
    4353          96 :     switch (es->format)
    4354             :     {
    4355          16 :         case EXPLAIN_FORMAT_TEXT:
    4356             :             /* nothing to do */
    4357          16 :             break;
    4358             : 
    4359           0 :         case EXPLAIN_FORMAT_XML:
    4360           0 :             es->indent -= depth;
    4361           0 :             break;
    4362             : 
    4363          80 :         case EXPLAIN_FORMAT_JSON:
    4364          80 :             es->indent -= depth;
    4365          80 :             *state_save = linitial_int(es->grouping_stack);
    4366          80 :             es->grouping_stack = list_delete_first(es->grouping_stack);
    4367          80 :             break;
    4368             : 
    4369           0 :         case EXPLAIN_FORMAT_YAML:
    4370           0 :             es->indent -= depth;
    4371           0 :             *state_save = linitial_int(es->grouping_stack);
    4372           0 :             es->grouping_stack = list_delete_first(es->grouping_stack);
    4373           0 :             break;
    4374             :     }
    4375          96 : }
    4376             : 
    4377             : /*
    4378             :  * Re-push one level of grouping state, undoing the effects of ExplainSaveGroup.
    4379             :  */
    4380             : static void
    4381          48 : ExplainRestoreGroup(ExplainState *es, int depth, int *state_save)
    4382             : {
    4383          48 :     switch (es->format)
    4384             :     {
    4385           0 :         case EXPLAIN_FORMAT_TEXT:
    4386             :             /* nothing to do */
    4387           0 :             break;
    4388             : 
    4389           0 :         case EXPLAIN_FORMAT_XML:
    4390           0 :             es->indent += depth;
    4391           0 :             break;
    4392             : 
    4393          48 :         case EXPLAIN_FORMAT_JSON:
    4394          48 :             es->grouping_stack = lcons_int(*state_save, es->grouping_stack);
    4395          48 :             es->indent += depth;
    4396          48 :             break;
    4397             : 
    4398           0 :         case EXPLAIN_FORMAT_YAML:
    4399           0 :             es->grouping_stack = lcons_int(*state_save, es->grouping_stack);
    4400           0 :             es->indent += depth;
    4401           0 :             break;
    4402             :     }
    4403          48 : }
    4404             : 
    4405             : /*
    4406             :  * Emit a "dummy" group that never has any members.
    4407             :  *
    4408             :  * objtype is the type of the group object, labelname is its label within
    4409             :  * a containing object (if any).
    4410             :  */
    4411             : static void
    4412           0 : ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
    4413             : {
    4414           0 :     switch (es->format)
    4415             :     {
    4416           0 :         case EXPLAIN_FORMAT_TEXT:
    4417             :             /* nothing to do */
    4418           0 :             break;
    4419             : 
    4420           0 :         case EXPLAIN_FORMAT_XML:
    4421           0 :             ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es);
    4422           0 :             break;
    4423             : 
    4424           0 :         case EXPLAIN_FORMAT_JSON:
    4425           0 :             ExplainJSONLineEnding(es);
    4426           0 :             appendStringInfoSpaces(es->str, 2 * es->indent);
    4427           0 :             if (labelname)
    4428             :             {
    4429           0 :                 escape_json(es->str, labelname);
    4430           0 :                 appendStringInfoString(es->str, ": ");
    4431             :             }
    4432           0 :             escape_json(es->str, objtype);
    4433           0 :             break;
    4434             : 
    4435           0 :         case EXPLAIN_FORMAT_YAML:
    4436           0 :             ExplainYAMLLineStarting(es);
    4437           0 :             if (labelname)
    4438             :             {
    4439           0 :                 escape_yaml(es->str, labelname);
    4440           0 :                 appendStringInfoString(es->str, ": ");
    4441             :             }
    4442             :             else
    4443             :             {
    4444           0 :                 appendStringInfoString(es->str, "- ");
    4445             :             }
    4446           0 :             escape_yaml(es->str, objtype);
    4447           0 :             break;
    4448             :     }
    4449           0 : }
    4450             : 
    4451             : /*
    4452             :  * Emit the start-of-output boilerplate.
    4453             :  *
    4454             :  * This is just enough different from processing a subgroup that we need
    4455             :  * a separate pair of subroutines.
    4456             :  */
    4457             : void
    4458       10398 : ExplainBeginOutput(ExplainState *es)
    4459             : {
    4460       10398 :     switch (es->format)
    4461             :     {
    4462       10246 :         case EXPLAIN_FORMAT_TEXT:
    4463             :             /* nothing to do */
    4464       10246 :             break;
    4465             : 
    4466           4 :         case EXPLAIN_FORMAT_XML:
    4467           4 :             appendStringInfoString(es->str,
    4468             :                                    "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
    4469           4 :             es->indent++;
    4470           4 :             break;
    4471             : 
    4472         144 :         case EXPLAIN_FORMAT_JSON:
    4473             :             /* top-level structure is an array of plans */
    4474         144 :             appendStringInfoChar(es->str, '[');
    4475         144 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
    4476         144 :             es->indent++;
    4477         144 :             break;
    4478             : 
    4479           4 :         case EXPLAIN_FORMAT_YAML:
    4480           4 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
    4481           4 :             break;
    4482             :     }
    4483       10398 : }
    4484             : 
    4485             : /*
    4486             :  * Emit the end-of-output boilerplate.
    4487             :  */
    4488             : void
    4489       10370 : ExplainEndOutput(ExplainState *es)
    4490             : {
    4491       10370 :     switch (es->format)
    4492             :     {
    4493       10218 :         case EXPLAIN_FORMAT_TEXT:
    4494             :             /* nothing to do */
    4495       10218 :             break;
    4496             : 
    4497           4 :         case EXPLAIN_FORMAT_XML:
    4498           4 :             es->indent--;
    4499           4 :             appendStringInfoString(es->str, "</explain>");
    4500           4 :             break;
    4501             : 
    4502         144 :         case EXPLAIN_FORMAT_JSON:
    4503         144 :             es->indent--;
    4504         144 :             appendStringInfoString(es->str, "\n]");
    4505         144 :             es->grouping_stack = list_delete_first(es->grouping_stack);
    4506         144 :             break;
    4507             : 
    4508           4 :         case EXPLAIN_FORMAT_YAML:
    4509           4 :             es->grouping_stack = list_delete_first(es->grouping_stack);
    4510           4 :             break;
    4511             :     }
    4512       10370 : }
    4513             : 
    4514             : /*
    4515             :  * Put an appropriate separator between multiple plans
    4516             :  */
    4517             : void
    4518           8 : ExplainSeparatePlans(ExplainState *es)
    4519             : {
    4520           8 :     switch (es->format)
    4521             :     {
    4522           8 :         case EXPLAIN_FORMAT_TEXT:
    4523             :             /* add a blank line */
    4524           8 :             appendStringInfoChar(es->str, '\n');
    4525           8 :             break;
    4526             : 
    4527           0 :         case EXPLAIN_FORMAT_XML:
    4528             :         case EXPLAIN_FORMAT_JSON:
    4529             :         case EXPLAIN_FORMAT_YAML:
    4530             :             /* nothing to do */
    4531           0 :             break;
    4532             :     }
    4533           8 : }
    4534             : 
    4535             : /*
    4536             :  * Emit opening or closing XML tag.
    4537             :  *
    4538             :  * "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE.
    4539             :  * Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally
    4540             :  * add.
    4541             :  *
    4542             :  * XML restricts tag names more than our other output formats, eg they can't
    4543             :  * contain white space or slashes.  Replace invalid characters with dashes,
    4544             :  * so that for example "I/O Read Time" becomes "I-O-Read-Time".
    4545             :  */
    4546             : static void
    4547         304 : ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
    4548             : {
    4549             :     const char *s;
    4550         304 :     const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";
    4551             : 
    4552         304 :     if ((flags & X_NOWHITESPACE) == 0)
    4553          32 :         appendStringInfoSpaces(es->str, 2 * es->indent);
    4554         304 :     appendStringInfoCharMacro(es->str, '<');
    4555         304 :     if ((flags & X_CLOSING) != 0)
    4556         152 :         appendStringInfoCharMacro(es->str, '/');
    4557        4808 :     for (s = tagname; *s; s++)
    4558        4504 :         appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-');
    4559         304 :     if ((flags & X_CLOSE_IMMEDIATE) != 0)
    4560           0 :         appendStringInfoString(es->str, " /");
    4561         304 :     appendStringInfoCharMacro(es->str, '>');
    4562         304 :     if ((flags & X_NOWHITESPACE) == 0)
    4563          32 :         appendStringInfoCharMacro(es->str, '\n');
    4564         304 : }
    4565             : 
    4566             : /*
    4567             :  * Indent a text-format line.
    4568             :  *
    4569             :  * We indent by two spaces per indentation level.  However, when emitting
    4570             :  * data for a parallel worker there might already be data on the current line
    4571             :  * (cf. ExplainOpenWorker); in that case, don't indent any more.
    4572             :  */
    4573             : static void
    4574       56192 : ExplainIndentText(ExplainState *es)
    4575             : {
    4576             :     Assert(es->format == EXPLAIN_FORMAT_TEXT);
    4577       56192 :     if (es->str->len == 0 || es->str->data[es->str->len - 1] == '\n')
    4578       56176 :         appendStringInfoSpaces(es->str, es->indent * 2);
    4579       56192 : }
    4580             : 
    4581             : /*
    4582             :  * Emit a JSON line ending.
    4583             :  *
    4584             :  * JSON requires a comma after each property but the last.  To facilitate this,
    4585             :  * in JSON format, the text emitted for each property begins just prior to the
    4586             :  * preceding line-break (and comma, if applicable).
    4587             :  */
    4588             : static void
    4589       11636 : ExplainJSONLineEnding(ExplainState *es)
    4590             : {
    4591             :     Assert(es->format == EXPLAIN_FORMAT_JSON);
    4592       11636 :     if (linitial_int(es->grouping_stack) != 0)
    4593       10008 :         appendStringInfoChar(es->str, ',');
    4594             :     else
    4595        1628 :         linitial_int(es->grouping_stack) = 1;
    4596       11636 :     appendStringInfoChar(es->str, '\n');
    4597       11636 : }
    4598             : 
    4599             : /*
    4600             :  * Indent a YAML line.
    4601             :  *
    4602             :  * YAML lines are ordinarily indented by two spaces per indentation level.
    4603             :  * The text emitted for each property begins just prior to the preceding
    4604             :  * line-break, except for the first property in an unlabelled group, for which
    4605             :  * it begins immediately after the "- " that introduces the group.  The first
    4606             :  * property of the group appears on the same line as the opening "- ".
    4607             :  */
    4608             : static void
    4609         152 : ExplainYAMLLineStarting(ExplainState *es)
    4610             : {
    4611             :     Assert(es->format == EXPLAIN_FORMAT_YAML);
    4612         152 :     if (linitial_int(es->grouping_stack) == 0)
    4613             :     {
    4614           8 :         linitial_int(es->grouping_stack) = 1;
    4615             :     }
    4616             :     else
    4617             :     {
    4618         144 :         appendStringInfoChar(es->str, '\n');
    4619         144 :         appendStringInfoSpaces(es->str, es->indent * 2);
    4620             :     }
    4621         152 : }
    4622             : 
    4623             : /*
    4624             :  * YAML is a superset of JSON; unfortunately, the YAML quoting rules are
    4625             :  * ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of
    4626             :  * http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything.
    4627             :  * Empty strings, strings with leading or trailing whitespace, and strings
    4628             :  * containing a variety of special characters must certainly be quoted or the
    4629             :  * output is invalid; and other seemingly harmless strings like "0xa" or
    4630             :  * "true" must be quoted, lest they be interpreted as a hexadecimal or Boolean
    4631             :  * constant rather than a string.
    4632             :  */
    4633             : static void
    4634          12 : escape_yaml(StringInfo buf, const char *str)
    4635             : {
    4636          12 :     escape_json(buf, str);
    4637          12 : }

Generated by: LCOV version 1.13