YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

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