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

Generated by: LCOV version 1.14