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

Generated by: LCOV version 1.14