1
/*-------------------------------------------------------------------------
4
* Explain query execution plans
6
* Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
7
* Portions Copyright (c) 1994-5, Regents of the University of California
10
* src/backend/commands/explain.c
12
*-------------------------------------------------------------------------
16
#include "access/xact.h"
17
#include "catalog/pg_constraint.h"
18
#include "catalog/pg_type.h"
19
#include "commands/defrem.h"
20
#include "commands/explain.h"
21
#include "commands/prepare.h"
22
#include "commands/trigger.h"
23
#include "executor/hashjoin.h"
24
#include "executor/instrument.h"
25
#include "foreign/fdwapi.h"
26
#include "optimizer/clauses.h"
27
#include "optimizer/planner.h"
28
#include "optimizer/var.h"
29
#include "parser/parsetree.h"
30
#include "rewrite/rewriteHandler.h"
31
#include "tcop/tcopprot.h"
32
#include "utils/builtins.h"
33
#include "utils/guc.h"
34
#include "utils/lsyscache.h"
35
#include "utils/tuplesort.h"
36
#include "utils/snapmgr.h"
37
#include "utils/xml.h"
40
/* Hook for plugins to get control in ExplainOneQuery() */
41
ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
43
/* Hook for plugins to get control in explain_get_index_name() */
44
explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
47
/* OR-able flags for ExplainXMLTag() */
50
#define X_CLOSE_IMMEDIATE 2
51
#define X_NOWHITESPACE 4
53
static void ExplainOneQuery(Query *query, ExplainState *es,
54
const char *queryString, ParamListInfo params);
55
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
57
static double elapsed_time(instr_time *starttime);
58
static void ExplainNode(PlanState *planstate, List *ancestors,
59
const char *relationship, const char *plan_name,
61
static void show_plan_tlist(PlanState *planstate, List *ancestors,
63
static void show_expression(Node *node, const char *qlabel,
64
PlanState *planstate, List *ancestors,
65
bool useprefix, ExplainState *es);
66
static void show_qual(List *qual, const char *qlabel,
67
PlanState *planstate, List *ancestors,
68
bool useprefix, ExplainState *es);
69
static void show_scan_qual(List *qual, const char *qlabel,
70
PlanState *planstate, List *ancestors,
72
static void show_upper_qual(List *qual, const char *qlabel,
73
PlanState *planstate, List *ancestors,
75
static void show_sort_keys(SortState *sortstate, List *ancestors,
77
static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
79
static void show_sort_keys_common(PlanState *planstate,
80
int nkeys, AttrNumber *keycols,
81
List *ancestors, ExplainState *es);
82
static void show_sort_info(SortState *sortstate, ExplainState *es);
83
static void show_hash_info(HashState *hashstate, ExplainState *es);
84
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
85
static const char *explain_get_index_name(Oid indexId);
86
static void ExplainScanTarget(Scan *plan, ExplainState *es);
87
static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
88
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
89
static void ExplainMemberNodes(List *plans, PlanState **planstates,
90
List *ancestors, ExplainState *es);
91
static void ExplainSubPlans(List *plans, List *ancestors,
92
const char *relationship, ExplainState *es);
93
static void ExplainProperty(const char *qlabel, const char *value,
94
bool numeric, ExplainState *es);
95
static void ExplainOpenGroup(const char *objtype, const char *labelname,
96
bool labeled, ExplainState *es);
97
static void ExplainCloseGroup(const char *objtype, const char *labelname,
98
bool labeled, ExplainState *es);
99
static void ExplainDummyGroup(const char *objtype, const char *labelname,
101
static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
102
static void ExplainJSONLineEnding(ExplainState *es);
103
static void ExplainYAMLLineStarting(ExplainState *es);
104
static void escape_json(StringInfo buf, const char *str);
105
static void escape_yaml(StringInfo buf, const char *str);
111
* execute an EXPLAIN command
114
ExplainQuery(ExplainStmt *stmt, const char *queryString,
115
ParamListInfo params, DestReceiver *dest)
118
TupOutputState *tstate;
122
/* Initialize ExplainState. */
123
ExplainInitState(&es);
125
/* Parse options list. */
126
foreach(lc, stmt->options)
128
DefElem *opt = (DefElem *) lfirst(lc);
130
if (strcmp(opt->defname, "analyze") == 0)
131
es.analyze = defGetBoolean(opt);
132
else if (strcmp(opt->defname, "verbose") == 0)
133
es.verbose = defGetBoolean(opt);
134
else if (strcmp(opt->defname, "costs") == 0)
135
es.costs = defGetBoolean(opt);
136
else if (strcmp(opt->defname, "buffers") == 0)
137
es.buffers = defGetBoolean(opt);
138
else if (strcmp(opt->defname, "format") == 0)
140
char *p = defGetString(opt);
142
if (strcmp(p, "text") == 0)
143
es.format = EXPLAIN_FORMAT_TEXT;
144
else if (strcmp(p, "xml") == 0)
145
es.format = EXPLAIN_FORMAT_XML;
146
else if (strcmp(p, "json") == 0)
147
es.format = EXPLAIN_FORMAT_JSON;
148
else if (strcmp(p, "yaml") == 0)
149
es.format = EXPLAIN_FORMAT_YAML;
152
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
153
errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
158
(errcode(ERRCODE_SYNTAX_ERROR),
159
errmsg("unrecognized EXPLAIN option \"%s\"",
163
if (es.buffers && !es.analyze)
165
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
166
errmsg("EXPLAIN option BUFFERS requires ANALYZE")));
169
* Parse analysis was done already, but we still have to run the rule
170
* rewriter. We do not do AcquireRewriteLocks: we assume the query either
171
* came straight from the parser, or suitable locks were acquired by
174
* Because the rewriter and planner tend to scribble on the input, we make
175
* a preliminary copy of the source querytree. This prevents problems in
176
* the case that the EXPLAIN is in a portal or plpgsql function and is
177
* executed repeatedly. (See also the same hack in DECLARE CURSOR and
178
* PREPARE.) XXX FIXME someday.
180
Assert(IsA(stmt->query, Query));
181
rewritten = QueryRewrite((Query *) copyObject(stmt->query));
183
/* emit opening boilerplate */
184
ExplainBeginOutput(&es);
186
if (rewritten == NIL)
189
* In the case of an INSTEAD NOTHING, tell at least that. But in
190
* non-text format, the output is delimited, so this isn't necessary.
192
if (es.format == EXPLAIN_FORMAT_TEXT)
193
appendStringInfoString(es.str, "Query rewrites to nothing\n");
199
/* Explain every plan */
200
foreach(l, rewritten)
202
ExplainOneQuery((Query *) lfirst(l), &es, queryString, params);
204
/* Separate plans with an appropriate separator */
205
if (lnext(l) != NULL)
206
ExplainSeparatePlans(&es);
210
/* emit closing boilerplate */
211
ExplainEndOutput(&es);
212
Assert(es.indent == 0);
215
tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
216
if (es.format == EXPLAIN_FORMAT_TEXT)
217
do_text_output_multiline(tstate, es.str->data);
219
do_text_output_oneline(tstate, es.str->data);
220
end_tup_output(tstate);
226
* Initialize ExplainState.
229
ExplainInitState(ExplainState *es)
231
/* Set default options. */
232
memset(es, 0, sizeof(ExplainState));
234
/* Prepare output buffer. */
235
es->str = makeStringInfo();
239
* ExplainResultDesc -
240
* construct the result tupledesc for an EXPLAIN
243
ExplainResultDesc(ExplainStmt *stmt)
249
/* Check for XML format option */
250
foreach(lc, stmt->options)
252
DefElem *opt = (DefElem *) lfirst(lc);
254
if (strcmp(opt->defname, "format") == 0)
256
char *p = defGetString(opt);
258
xml = (strcmp(p, "xml") == 0);
259
/* don't "break", as ExplainQuery will use the last value */
263
/* Need a tuple descriptor representing a single TEXT or XML column */
264
tupdesc = CreateTemplateTupleDesc(1, false);
265
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
266
xml ? XMLOID : TEXTOID, -1, 0);
272
* print out the execution plan for one Query
275
ExplainOneQuery(Query *query, ExplainState *es,
276
const char *queryString, ParamListInfo params)
278
/* planner will not cope with utility statements */
279
if (query->commandType == CMD_UTILITY)
281
ExplainOneUtility(query->utilityStmt, es, queryString, params);
285
/* if an advisor plugin is present, let it manage things */
286
if (ExplainOneQuery_hook)
287
(*ExplainOneQuery_hook) (query, es, queryString, params);
293
plan = pg_plan_query(query, 0, params);
295
/* run it (if needed) and produce output */
296
ExplainOnePlan(plan, es, queryString, params);
301
* ExplainOneUtility -
302
* print out the execution plan for one utility statement
303
* (In general, utility statements don't have plans, but there are some
304
* we treat as special cases)
306
* This is exported because it's called back from prepare.c in the
307
* EXPLAIN EXECUTE case
310
ExplainOneUtility(Node *utilityStmt, ExplainState *es,
311
const char *queryString, ParamListInfo params)
313
if (utilityStmt == NULL)
316
if (IsA(utilityStmt, ExecuteStmt))
317
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es,
318
queryString, params);
319
else if (IsA(utilityStmt, NotifyStmt))
321
if (es->format == EXPLAIN_FORMAT_TEXT)
322
appendStringInfoString(es->str, "NOTIFY\n");
324
ExplainDummyGroup("Notify", NULL, es);
328
if (es->format == EXPLAIN_FORMAT_TEXT)
329
appendStringInfoString(es->str,
330
"Utility statements have no plan structure\n");
332
ExplainDummyGroup("Utility Statement", NULL, es);
338
* given a planned query, execute it if needed, and then print
341
* Since we ignore any DeclareCursorStmt that might be attached to the query,
342
* if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll actually run the
343
* query. This is different from pre-8.3 behavior but seems more useful than
344
* not running the query. No cursor will be created, however.
346
* This is exported because it's called back from prepare.c in the
347
* EXPLAIN EXECUTE case, and because an index advisor plugin would need
351
ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
352
const char *queryString, ParamListInfo params)
354
QueryDesc *queryDesc;
355
instr_time starttime;
356
double totaltime = 0;
358
int instrument_option = 0;
361
instrument_option |= INSTRUMENT_TIMER;
363
instrument_option |= INSTRUMENT_BUFFERS;
365
INSTR_TIME_SET_CURRENT(starttime);
368
* Use a snapshot with an updated command ID to ensure this query sees
369
* results of any previously executed queries.
371
PushCopiedSnapshot(GetActiveSnapshot());
372
UpdateActiveSnapshotCommandId();
374
/* Create a QueryDesc requesting no output */
375
queryDesc = CreateQueryDesc(plannedstmt, queryString,
376
GetActiveSnapshot(), InvalidSnapshot,
377
None_Receiver, params, instrument_option);
379
/* Select execution options */
381
eflags = 0; /* default run-to-completion flags */
383
eflags = EXEC_FLAG_EXPLAIN_ONLY;
385
/* call ExecutorStart to prepare the plan for execution */
386
ExecutorStart(queryDesc, eflags);
388
/* Execute the plan for statistics if asked for */
392
ExecutorRun(queryDesc, ForwardScanDirection, 0L);
394
/* run cleanup too */
395
ExecutorFinish(queryDesc);
397
/* We can't run ExecutorEnd 'till we're done printing the stats... */
398
totaltime += elapsed_time(&starttime);
401
ExplainOpenGroup("Query", NULL, true, es);
403
/* Create textual dump of plan tree */
404
ExplainPrintPlan(es, queryDesc);
406
/* Print info about runtime of triggers */
409
ResultRelInfo *rInfo;
411
int numrels = queryDesc->estate->es_num_result_relations;
412
List *targrels = queryDesc->estate->es_trig_target_relations;
416
ExplainOpenGroup("Triggers", "Triggers", false, es);
418
show_relname = (numrels > 1 || targrels != NIL);
419
rInfo = queryDesc->estate->es_result_relations;
420
for (nr = 0; nr < numrels; rInfo++, nr++)
421
report_triggers(rInfo, show_relname, es);
425
rInfo = (ResultRelInfo *) lfirst(l);
426
report_triggers(rInfo, show_relname, es);
429
ExplainCloseGroup("Triggers", "Triggers", false, es);
433
* Close down the query and free resources. Include time for this in the
434
* total runtime (although it should be pretty minimal).
436
INSTR_TIME_SET_CURRENT(starttime);
438
ExecutorEnd(queryDesc);
440
FreeQueryDesc(queryDesc);
444
/* We need a CCI just in case query expanded to multiple plans */
446
CommandCounterIncrement();
448
totaltime += elapsed_time(&starttime);
452
if (es->format == EXPLAIN_FORMAT_TEXT)
453
appendStringInfo(es->str, "Total runtime: %.3f ms\n",
456
ExplainPropertyFloat("Total Runtime", 1000.0 * totaltime,
460
ExplainCloseGroup("Query", NULL, true, es);
465
* convert a QueryDesc's plan tree to text and append it to es->str
467
* The caller should have set up the options fields of *es, as well as
468
* initializing the output buffer es->str. Other fields in *es are
471
* NB: will not work on utility statements
474
ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
476
Assert(queryDesc->plannedstmt != NULL);
477
es->pstmt = queryDesc->plannedstmt;
478
es->rtable = queryDesc->plannedstmt->rtable;
479
ExplainNode(queryDesc->planstate, NIL, NULL, NULL, es);
484
* add a "Query Text" node that contains the actual text of the query
486
* The caller should have set up the options fields of *es, as well as
487
* initializing the output buffer es->str.
491
ExplainQueryText(ExplainState *es, QueryDesc *queryDesc)
493
if (queryDesc->sourceText)
494
ExplainPropertyText("Query Text", queryDesc->sourceText, es);
499
* report execution stats for a single relation's triggers
502
report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es)
506
if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
508
for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
510
Trigger *trig = rInfo->ri_TrigDesc->triggers + nt;
511
Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
513
char *conname = NULL;
515
/* Must clean up instrumentation state */
519
* We ignore triggers that were never invoked; they likely aren't
520
* relevant to the current query type.
522
if (instr->ntuples == 0)
525
ExplainOpenGroup("Trigger", NULL, true, es);
527
relname = RelationGetRelationName(rInfo->ri_RelationDesc);
528
if (OidIsValid(trig->tgconstraint))
529
conname = get_constraint_name(trig->tgconstraint);
532
* In text format, we avoid printing both the trigger name and the
533
* constraint name unless VERBOSE is specified. In non-text formats
534
* we just print everything.
536
if (es->format == EXPLAIN_FORMAT_TEXT)
538
if (es->verbose || conname == NULL)
539
appendStringInfo(es->str, "Trigger %s", trig->tgname);
541
appendStringInfoString(es->str, "Trigger");
543
appendStringInfo(es->str, " for constraint %s", conname);
545
appendStringInfo(es->str, " on %s", relname);
546
appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",
547
1000.0 * instr->total, instr->ntuples);
551
ExplainPropertyText("Trigger Name", trig->tgname, es);
553
ExplainPropertyText("Constraint Name", conname, es);
554
ExplainPropertyText("Relation", relname, es);
555
ExplainPropertyFloat("Time", 1000.0 * instr->total, 3, es);
556
ExplainPropertyFloat("Calls", instr->ntuples, 0, es);
562
ExplainCloseGroup("Trigger", NULL, true, es);
566
/* Compute elapsed time in seconds since given timestamp */
568
elapsed_time(instr_time *starttime)
572
INSTR_TIME_SET_CURRENT(endtime);
573
INSTR_TIME_SUBTRACT(endtime, *starttime);
574
return INSTR_TIME_GET_DOUBLE(endtime);
579
* Appends a description of a plan tree to es->str
581
* planstate points to the executor state node for the current plan node.
582
* We need to work from a PlanState node, not just a Plan node, in order to
583
* get at the instrumentation data (if any) as well as the list of subplans.
585
* ancestors is a list of parent PlanState nodes, most-closely-nested first.
586
* These are needed in order to interpret PARAM_EXEC Params.
588
* relationship describes the relationship of this plan node to its parent
589
* (eg, "Outer", "Inner"); it can be null at top level. plan_name is an
590
* optional name to be attached to the node.
592
* In text format, es->indent is controlled in this function since we only
593
* want it to change at plan-node boundaries. In non-text formats, es->indent
594
* corresponds to the nesting depth of logical output groups, and therefore
595
* is controlled by ExplainOpenGroup/ExplainCloseGroup.
598
ExplainNode(PlanState *planstate, List *ancestors,
599
const char *relationship, const char *plan_name,
602
Plan *plan = planstate->plan;
603
const char *pname; /* node type name for text output */
604
const char *sname; /* node type name for non-text output */
605
const char *strategy = NULL;
606
const char *operation = NULL;
607
int save_indent = es->indent;
610
switch (nodeTag(plan))
613
pname = sname = "Result";
616
sname = "ModifyTable";
617
switch (((ModifyTable *) plan)->operation)
620
pname = operation = "Insert";
623
pname = operation = "Update";
626
pname = operation = "Delete";
634
pname = sname = "Append";
637
pname = sname = "Merge Append";
639
case T_RecursiveUnion:
640
pname = sname = "Recursive Union";
643
pname = sname = "BitmapAnd";
646
pname = sname = "BitmapOr";
649
pname = sname = "Nested Loop";
652
pname = "Merge"; /* "Join" gets added by jointype switch */
653
sname = "Merge Join";
656
pname = "Hash"; /* "Join" gets added by jointype switch */
660
pname = sname = "Seq Scan";
663
pname = sname = "Index Scan";
665
case T_BitmapIndexScan:
666
pname = sname = "Bitmap Index Scan";
668
case T_BitmapHeapScan:
669
pname = sname = "Bitmap Heap Scan";
672
pname = sname = "Tid Scan";
675
pname = sname = "Subquery Scan";
678
pname = sname = "Function Scan";
681
pname = sname = "Values Scan";
684
pname = sname = "CTE Scan";
686
case T_WorkTableScan:
687
pname = sname = "WorkTable Scan";
690
pname = sname = "Foreign Scan";
693
pname = sname = "Materialize";
696
pname = sname = "Sort";
699
pname = sname = "Group";
703
switch (((Agg *) plan)->aggstrategy)
710
pname = "GroupAggregate";
714
pname = "HashAggregate";
718
pname = "Aggregate ???";
724
pname = sname = "WindowAgg";
727
pname = sname = "Unique";
731
switch (((SetOp *) plan)->strategy)
748
pname = sname = "LockRows";
751
pname = sname = "Limit";
754
pname = sname = "Hash";
757
pname = sname = "???";
761
ExplainOpenGroup("Plan",
762
relationship ? NULL : "Plan",
765
if (es->format == EXPLAIN_FORMAT_TEXT)
769
appendStringInfoSpaces(es->str, es->indent * 2);
770
appendStringInfo(es->str, "%s\n", plan_name);
775
appendStringInfoSpaces(es->str, es->indent * 2);
776
appendStringInfoString(es->str, "-> ");
779
appendStringInfoString(es->str, pname);
784
ExplainPropertyText("Node Type", sname, es);
786
ExplainPropertyText("Strategy", strategy, es);
788
ExplainPropertyText("Operation", operation, es);
790
ExplainPropertyText("Parent Relationship", relationship, es);
792
ExplainPropertyText("Subplan Name", plan_name, es);
795
switch (nodeTag(plan))
799
IndexScan *indexscan = (IndexScan *) plan;
800
const char *indexname =
801
explain_get_index_name(indexscan->indexid);
803
if (es->format == EXPLAIN_FORMAT_TEXT)
805
if (ScanDirectionIsBackward(indexscan->indexorderdir))
806
appendStringInfoString(es->str, " Backward");
807
appendStringInfo(es->str, " using %s", indexname);
813
switch (indexscan->indexorderdir)
815
case BackwardScanDirection:
816
scandir = "Backward";
818
case NoMovementScanDirection:
819
scandir = "NoMovement";
821
case ForwardScanDirection:
828
ExplainPropertyText("Scan Direction", scandir, es);
829
ExplainPropertyText("Index Name", indexname, es);
834
case T_BitmapHeapScan:
840
case T_WorkTableScan:
842
ExplainScanTarget((Scan *) plan, es);
844
case T_BitmapIndexScan:
846
BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
847
const char *indexname =
848
explain_get_index_name(bitmapindexscan->indexid);
850
if (es->format == EXPLAIN_FORMAT_TEXT)
851
appendStringInfo(es->str, " on %s", indexname);
853
ExplainPropertyText("Index Name", indexname, es);
857
ExplainModifyTarget((ModifyTable *) plan, es);
863
const char *jointype;
865
switch (((Join *) plan)->jointype)
889
if (es->format == EXPLAIN_FORMAT_TEXT)
892
* For historical reasons, the join type is interpolated
893
* into the node type name...
895
if (((Join *) plan)->jointype != JOIN_INNER)
896
appendStringInfo(es->str, " %s Join", jointype);
897
else if (!IsA(plan, NestLoop))
898
appendStringInfo(es->str, " Join");
901
ExplainPropertyText("Join Type", jointype, es);
906
const char *setopcmd;
908
switch (((SetOp *) plan)->cmd)
910
case SETOPCMD_INTERSECT:
911
setopcmd = "Intersect";
913
case SETOPCMD_INTERSECT_ALL:
914
setopcmd = "Intersect All";
916
case SETOPCMD_EXCEPT:
919
case SETOPCMD_EXCEPT_ALL:
920
setopcmd = "Except All";
926
if (es->format == EXPLAIN_FORMAT_TEXT)
927
appendStringInfo(es->str, " %s", setopcmd);
929
ExplainPropertyText("Command", setopcmd, es);
938
if (es->format == EXPLAIN_FORMAT_TEXT)
940
appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
941
plan->startup_cost, plan->total_cost,
942
plan->plan_rows, plan->plan_width);
946
ExplainPropertyFloat("Startup Cost", plan->startup_cost, 2, es);
947
ExplainPropertyFloat("Total Cost", plan->total_cost, 2, es);
948
ExplainPropertyFloat("Plan Rows", plan->plan_rows, 0, es);
949
ExplainPropertyInteger("Plan Width", plan->plan_width, es);
954
* We have to forcibly clean up the instrumentation state because we
955
* haven't done ExecutorEnd yet. This is pretty grotty ...
957
if (planstate->instrument)
958
InstrEndLoop(planstate->instrument);
960
if (planstate->instrument && planstate->instrument->nloops > 0)
962
double nloops = planstate->instrument->nloops;
963
double startup_sec = 1000.0 * planstate->instrument->startup / nloops;
964
double total_sec = 1000.0 * planstate->instrument->total / nloops;
965
double rows = planstate->instrument->ntuples / nloops;
967
if (es->format == EXPLAIN_FORMAT_TEXT)
969
appendStringInfo(es->str,
970
" (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
971
startup_sec, total_sec, rows, nloops);
975
ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
976
ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
977
ExplainPropertyFloat("Actual Rows", rows, 0, es);
978
ExplainPropertyFloat("Actual Loops", nloops, 0, es);
981
else if (es->analyze)
983
if (es->format == EXPLAIN_FORMAT_TEXT)
984
appendStringInfo(es->str, " (never executed)");
987
ExplainPropertyFloat("Actual Startup Time", 0.0, 3, es);
988
ExplainPropertyFloat("Actual Total Time", 0.0, 3, es);
989
ExplainPropertyFloat("Actual Rows", 0.0, 0, es);
990
ExplainPropertyFloat("Actual Loops", 0.0, 0, es);
994
/* in text format, first line ends here */
995
if (es->format == EXPLAIN_FORMAT_TEXT)
996
appendStringInfoChar(es->str, '\n');
1000
show_plan_tlist(planstate, ancestors, es);
1002
/* quals, sort keys, etc */
1003
switch (nodeTag(plan))
1006
show_scan_qual(((IndexScan *) plan)->indexqualorig,
1007
"Index Cond", planstate, ancestors, es);
1008
show_scan_qual(((IndexScan *) plan)->indexorderbyorig,
1009
"Order By", planstate, ancestors, es);
1010
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1012
case T_BitmapIndexScan:
1013
show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
1014
"Index Cond", planstate, ancestors, es);
1016
case T_BitmapHeapScan:
1017
show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
1018
"Recheck Cond", planstate, ancestors, es);
1023
case T_WorkTableScan:
1024
case T_SubqueryScan:
1025
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1027
case T_FunctionScan:
1029
show_expression(((FunctionScan *) plan)->funcexpr,
1030
"Function Call", planstate, ancestors,
1032
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1037
* The tidquals list has OR semantics, so be sure to show it
1038
* as an OR condition.
1040
List *tidquals = ((TidScan *) plan)->tidquals;
1042
if (list_length(tidquals) > 1)
1043
tidquals = list_make1(make_orclause(tidquals));
1044
show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
1045
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1049
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
1050
show_foreignscan_info((ForeignScanState *) planstate, es);
1053
show_upper_qual(((NestLoop *) plan)->join.joinqual,
1054
"Join Filter", planstate, ancestors, es);
1055
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1058
show_upper_qual(((MergeJoin *) plan)->mergeclauses,
1059
"Merge Cond", planstate, ancestors, es);
1060
show_upper_qual(((MergeJoin *) plan)->join.joinqual,
1061
"Join Filter", planstate, ancestors, es);
1062
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1065
show_upper_qual(((HashJoin *) plan)->hashclauses,
1066
"Hash Cond", planstate, ancestors, es);
1067
show_upper_qual(((HashJoin *) plan)->join.joinqual,
1068
"Join Filter", planstate, ancestors, es);
1069
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1073
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1076
show_sort_keys((SortState *) planstate, ancestors, es);
1077
show_sort_info((SortState *) planstate, es);
1080
show_merge_append_keys((MergeAppendState *) planstate,
1084
show_upper_qual((List *) ((Result *) plan)->resconstantqual,
1085
"One-Time Filter", planstate, ancestors, es);
1086
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
1089
show_hash_info((HashState *) planstate, es);
1095
/* Show buffer usage */
1098
const BufferUsage *usage = &planstate->instrument->bufusage;
1100
if (es->format == EXPLAIN_FORMAT_TEXT)
1102
bool has_shared = (usage->shared_blks_hit > 0 ||
1103
usage->shared_blks_read > 0 ||
1104
usage->shared_blks_written);
1105
bool has_local = (usage->local_blks_hit > 0 ||
1106
usage->local_blks_read > 0 ||
1107
usage->local_blks_written);
1108
bool has_temp = (usage->temp_blks_read > 0 ||
1109
usage->temp_blks_written);
1111
/* Show only positive counter values. */
1112
if (has_shared || has_local || has_temp)
1114
appendStringInfoSpaces(es->str, es->indent * 2);
1115
appendStringInfoString(es->str, "Buffers:");
1119
appendStringInfoString(es->str, " shared");
1120
if (usage->shared_blks_hit > 0)
1121
appendStringInfo(es->str, " hit=%ld",
1122
usage->shared_blks_hit);
1123
if (usage->shared_blks_read > 0)
1124
appendStringInfo(es->str, " read=%ld",
1125
usage->shared_blks_read);
1126
if (usage->shared_blks_written > 0)
1127
appendStringInfo(es->str, " written=%ld",
1128
usage->shared_blks_written);
1129
if (has_local || has_temp)
1130
appendStringInfoChar(es->str, ',');
1134
appendStringInfoString(es->str, " local");
1135
if (usage->local_blks_hit > 0)
1136
appendStringInfo(es->str, " hit=%ld",
1137
usage->local_blks_hit);
1138
if (usage->local_blks_read > 0)
1139
appendStringInfo(es->str, " read=%ld",
1140
usage->local_blks_read);
1141
if (usage->local_blks_written > 0)
1142
appendStringInfo(es->str, " written=%ld",
1143
usage->local_blks_written);
1145
appendStringInfoChar(es->str, ',');
1149
appendStringInfoString(es->str, " temp");
1150
if (usage->temp_blks_read > 0)
1151
appendStringInfo(es->str, " read=%ld",
1152
usage->temp_blks_read);
1153
if (usage->temp_blks_written > 0)
1154
appendStringInfo(es->str, " written=%ld",
1155
usage->temp_blks_written);
1157
appendStringInfoChar(es->str, '\n');
1162
ExplainPropertyLong("Shared Hit Blocks", usage->shared_blks_hit, es);
1163
ExplainPropertyLong("Shared Read Blocks", usage->shared_blks_read, es);
1164
ExplainPropertyLong("Shared Written Blocks", usage->shared_blks_written, es);
1165
ExplainPropertyLong("Local Hit Blocks", usage->local_blks_hit, es);
1166
ExplainPropertyLong("Local Read Blocks", usage->local_blks_read, es);
1167
ExplainPropertyLong("Local Written Blocks", usage->local_blks_written, es);
1168
ExplainPropertyLong("Temp Read Blocks", usage->temp_blks_read, es);
1169
ExplainPropertyLong("Temp Written Blocks", usage->temp_blks_written, es);
1173
/* Get ready to display the child plans */
1174
haschildren = planstate->initPlan ||
1175
outerPlanState(planstate) ||
1176
innerPlanState(planstate) ||
1177
IsA(plan, ModifyTable) ||
1178
IsA(plan, Append) ||
1179
IsA(plan, MergeAppend) ||
1180
IsA(plan, BitmapAnd) ||
1181
IsA(plan, BitmapOr) ||
1182
IsA(plan, SubqueryScan) ||
1186
ExplainOpenGroup("Plans", "Plans", false, es);
1187
/* Pass current PlanState as head of ancestors list for children */
1188
ancestors = lcons(planstate, ancestors);
1192
if (planstate->initPlan)
1193
ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es);
1196
if (outerPlanState(planstate))
1197
ExplainNode(outerPlanState(planstate), ancestors,
1201
if (innerPlanState(planstate))
1202
ExplainNode(innerPlanState(planstate), ancestors,
1205
/* special child plans */
1206
switch (nodeTag(plan))
1209
ExplainMemberNodes(((ModifyTable *) plan)->plans,
1210
((ModifyTableState *) planstate)->mt_plans,
1214
ExplainMemberNodes(((Append *) plan)->appendplans,
1215
((AppendState *) planstate)->appendplans,
1219
ExplainMemberNodes(((MergeAppend *) plan)->mergeplans,
1220
((MergeAppendState *) planstate)->mergeplans,
1224
ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
1225
((BitmapAndState *) planstate)->bitmapplans,
1229
ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
1230
((BitmapOrState *) planstate)->bitmapplans,
1233
case T_SubqueryScan:
1234
ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,
1235
"Subquery", NULL, es);
1242
if (planstate->subPlan)
1243
ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es);
1245
/* end of child plans */
1248
ancestors = list_delete_first(ancestors);
1249
ExplainCloseGroup("Plans", "Plans", false, es);
1252
/* in text format, undo whatever indentation we added */
1253
if (es->format == EXPLAIN_FORMAT_TEXT)
1254
es->indent = save_indent;
1256
ExplainCloseGroup("Plan",
1257
relationship ? NULL : "Plan",
1262
* Show the targetlist of a plan node
1265
show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
1267
Plan *plan = planstate->plan;
1273
/* No work if empty tlist (this occurs eg in bitmap indexscans) */
1274
if (plan->targetlist == NIL)
1276
/* The tlist of an Append isn't real helpful, so suppress it */
1277
if (IsA(plan, Append))
1279
/* Likewise for MergeAppend and RecursiveUnion */
1280
if (IsA(plan, MergeAppend))
1282
if (IsA(plan, RecursiveUnion))
1285
/* Set up deparsing context */
1286
context = deparse_context_for_planstate((Node *) planstate,
1289
useprefix = list_length(es->rtable) > 1;
1291
/* Deparse each result column (we now include resjunk ones) */
1292
foreach(lc, plan->targetlist)
1294
TargetEntry *tle = (TargetEntry *) lfirst(lc);
1296
result = lappend(result,
1297
deparse_expression((Node *) tle->expr, context,
1302
ExplainPropertyList("Output", result, es);
1306
* Show a generic expression
1309
show_expression(Node *node, const char *qlabel,
1310
PlanState *planstate, List *ancestors,
1311
bool useprefix, ExplainState *es)
1316
/* Set up deparsing context */
1317
context = deparse_context_for_planstate((Node *) planstate,
1321
/* Deparse the expression */
1322
exprstr = deparse_expression(node, context, useprefix, false);
1324
/* And add to es->str */
1325
ExplainPropertyText(qlabel, exprstr, es);
1329
* Show a qualifier expression (which is a List with implicit AND semantics)
1332
show_qual(List *qual, const char *qlabel,
1333
PlanState *planstate, List *ancestors,
1334
bool useprefix, ExplainState *es)
1338
/* No work if empty qual */
1342
/* Convert AND list to explicit AND */
1343
node = (Node *) make_ands_explicit(qual);
1346
show_expression(node, qlabel, planstate, ancestors, useprefix, es);
1350
* Show a qualifier expression for a scan plan node
1353
show_scan_qual(List *qual, const char *qlabel,
1354
PlanState *planstate, List *ancestors,
1359
useprefix = (IsA(planstate->plan, SubqueryScan) ||es->verbose);
1360
show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
1364
* Show a qualifier expression for an upper-level plan node
1367
show_upper_qual(List *qual, const char *qlabel,
1368
PlanState *planstate, List *ancestors,
1373
useprefix = (list_length(es->rtable) > 1 || es->verbose);
1374
show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
1378
* Show the sort keys for a Sort node.
1381
show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
1383
Sort *plan = (Sort *) sortstate->ss.ps.plan;
1385
show_sort_keys_common((PlanState *) sortstate,
1386
plan->numCols, plan->sortColIdx,
1391
* Likewise, for a MergeAppend node.
1394
show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
1397
MergeAppend *plan = (MergeAppend *) mstate->ps.plan;
1399
show_sort_keys_common((PlanState *) mstate,
1400
plan->numCols, plan->sortColIdx,
1405
show_sort_keys_common(PlanState *planstate, int nkeys, AttrNumber *keycols,
1406
List *ancestors, ExplainState *es)
1408
Plan *plan = planstate->plan;
1418
/* Set up deparsing context */
1419
context = deparse_context_for_planstate((Node *) planstate,
1422
useprefix = (list_length(es->rtable) > 1 || es->verbose);
1424
for (keyno = 0; keyno < nkeys; keyno++)
1426
/* find key expression in tlist */
1427
AttrNumber keyresno = keycols[keyno];
1428
TargetEntry *target = get_tle_by_resno(plan->targetlist,
1432
elog(ERROR, "no tlist entry for key %d", keyresno);
1433
/* Deparse the expression, showing any top-level cast */
1434
exprstr = deparse_expression((Node *) target->expr, context,
1436
result = lappend(result, exprstr);
1439
ExplainPropertyList("Sort Key", result, es);
1443
* If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
1446
show_sort_info(SortState *sortstate, ExplainState *es)
1448
Assert(IsA(sortstate, SortState));
1449
if (es->analyze && sortstate->sort_Done &&
1450
sortstate->tuplesortstate != NULL)
1452
Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;
1453
const char *sortMethod;
1454
const char *spaceType;
1457
tuplesort_get_stats(state, &sortMethod, &spaceType, &spaceUsed);
1459
if (es->format == EXPLAIN_FORMAT_TEXT)
1461
appendStringInfoSpaces(es->str, es->indent * 2);
1462
appendStringInfo(es->str, "Sort Method: %s %s: %ldkB\n",
1463
sortMethod, spaceType, spaceUsed);
1467
ExplainPropertyText("Sort Method", sortMethod, es);
1468
ExplainPropertyLong("Sort Space Used", spaceUsed, es);
1469
ExplainPropertyText("Sort Space Type", spaceType, es);
1475
* Show information on hash buckets/batches.
1478
show_hash_info(HashState *hashstate, ExplainState *es)
1480
HashJoinTable hashtable;
1482
Assert(IsA(hashstate, HashState));
1483
hashtable = hashstate->hashtable;
1487
long spacePeakKb = (hashtable->spacePeak + 1023) / 1024;
1489
if (es->format != EXPLAIN_FORMAT_TEXT)
1491
ExplainPropertyLong("Hash Buckets", hashtable->nbuckets, es);
1492
ExplainPropertyLong("Hash Batches", hashtable->nbatch, es);
1493
ExplainPropertyLong("Original Hash Batches",
1494
hashtable->nbatch_original, es);
1495
ExplainPropertyLong("Peak Memory Usage", spacePeakKb, es);
1497
else if (hashtable->nbatch_original != hashtable->nbatch)
1499
appendStringInfoSpaces(es->str, es->indent * 2);
1500
appendStringInfo(es->str,
1501
"Buckets: %d Batches: %d (originally %d) Memory Usage: %ldkB\n",
1502
hashtable->nbuckets, hashtable->nbatch,
1503
hashtable->nbatch_original, spacePeakKb);
1507
appendStringInfoSpaces(es->str, es->indent * 2);
1508
appendStringInfo(es->str,
1509
"Buckets: %d Batches: %d Memory Usage: %ldkB\n",
1510
hashtable->nbuckets, hashtable->nbatch,
1517
* Show extra information for a ForeignScan node.
1520
show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
1522
FdwRoutine *fdwroutine = fsstate->fdwroutine;
1524
/* Let the FDW emit whatever fields it wants */
1525
fdwroutine->ExplainForeignScan(fsstate, es);
1529
* Fetch the name of an index in an EXPLAIN
1531
* We allow plugins to get control here so that plans involving hypothetical
1532
* indexes can be explained.
1535
explain_get_index_name(Oid indexId)
1539
if (explain_get_index_name_hook)
1540
result = (*explain_get_index_name_hook) (indexId);
1545
/* default behavior: look in the catalogs and quote it */
1546
result = get_rel_name(indexId);
1548
elog(ERROR, "cache lookup failed for index %u", indexId);
1549
result = quote_identifier(result);
1555
* Show the target of a Scan node
1558
ExplainScanTarget(Scan *plan, ExplainState *es)
1560
ExplainTargetRel((Plan *) plan, plan->scanrelid, es);
1564
* Show the target of a ModifyTable node
1567
ExplainModifyTarget(ModifyTable *plan, ExplainState *es)
1572
* We show the name of the first target relation. In multi-target-table
1573
* cases this should always be the parent of the inheritance tree.
1575
Assert(plan->resultRelations != NIL);
1576
rti = linitial_int(plan->resultRelations);
1578
ExplainTargetRel((Plan *) plan, rti, es);
1582
* Show the target relation of a scan or modify node
1585
ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
1587
char *objectname = NULL;
1588
char *namespace = NULL;
1589
const char *objecttag = NULL;
1592
rte = rt_fetch(rti, es->rtable);
1594
switch (nodeTag(plan))
1598
case T_BitmapHeapScan:
1602
/* Assert it's on a real relation */
1603
Assert(rte->rtekind == RTE_RELATION);
1604
objectname = get_rel_name(rte->relid);
1606
namespace = get_namespace_name(get_rel_namespace(rte->relid));
1607
objecttag = "Relation Name";
1609
case T_FunctionScan:
1613
/* Assert it's on a RangeFunction */
1614
Assert(rte->rtekind == RTE_FUNCTION);
1617
* If the expression is still a function call, we can get the
1618
* real name of the function. Otherwise, punt (this can
1619
* happen if the optimizer simplified away the function call,
1622
funcexpr = ((FunctionScan *) plan)->funcexpr;
1623
if (funcexpr && IsA(funcexpr, FuncExpr))
1625
Oid funcid = ((FuncExpr *) funcexpr)->funcid;
1627
objectname = get_func_name(funcid);
1630
get_namespace_name(get_func_namespace(funcid));
1632
objecttag = "Function Name";
1636
Assert(rte->rtekind == RTE_VALUES);
1639
/* Assert it's on a non-self-reference CTE */
1640
Assert(rte->rtekind == RTE_CTE);
1641
Assert(!rte->self_reference);
1642
objectname = rte->ctename;
1643
objecttag = "CTE Name";
1645
case T_WorkTableScan:
1646
/* Assert it's on a self-reference CTE */
1647
Assert(rte->rtekind == RTE_CTE);
1648
Assert(rte->self_reference);
1649
objectname = rte->ctename;
1650
objecttag = "CTE Name";
1656
if (es->format == EXPLAIN_FORMAT_TEXT)
1658
appendStringInfoString(es->str, " on");
1659
if (namespace != NULL)
1660
appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
1661
quote_identifier(objectname));
1662
else if (objectname != NULL)
1663
appendStringInfo(es->str, " %s", quote_identifier(objectname));
1664
if (objectname == NULL ||
1665
strcmp(rte->eref->aliasname, objectname) != 0)
1666
appendStringInfo(es->str, " %s",
1667
quote_identifier(rte->eref->aliasname));
1671
if (objecttag != NULL && objectname != NULL)
1672
ExplainPropertyText(objecttag, objectname, es);
1673
if (namespace != NULL)
1674
ExplainPropertyText("Schema", namespace, es);
1675
ExplainPropertyText("Alias", rte->eref->aliasname, es);
1680
* Explain the constituent plans of a ModifyTable, Append, MergeAppend,
1681
* BitmapAnd, or BitmapOr node.
1683
* The ancestors list should already contain the immediate parent of these
1686
* Note: we don't actually need to examine the Plan list members, but
1687
* we need the list in order to determine the length of the PlanState array.
1690
ExplainMemberNodes(List *plans, PlanState **planstates,
1691
List *ancestors, ExplainState *es)
1693
int nplans = list_length(plans);
1696
for (j = 0; j < nplans; j++)
1697
ExplainNode(planstates[j], ancestors,
1698
"Member", NULL, es);
1702
* Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).
1704
* The ancestors list should already contain the immediate parent of these
1708
ExplainSubPlans(List *plans, List *ancestors,
1709
const char *relationship, ExplainState *es)
1715
SubPlanState *sps = (SubPlanState *) lfirst(lst);
1716
SubPlan *sp = (SubPlan *) sps->xprstate.expr;
1718
ExplainNode(sps->planstate, ancestors,
1719
relationship, sp->plan_name, es);
1724
* Explain a property, such as sort keys or targets, that takes the form of
1725
* a list of unlabeled items. "data" is a list of C strings.
1728
ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
1735
case EXPLAIN_FORMAT_TEXT:
1736
appendStringInfoSpaces(es->str, es->indent * 2);
1737
appendStringInfo(es->str, "%s: ", qlabel);
1741
appendStringInfoString(es->str, ", ");
1742
appendStringInfoString(es->str, (const char *) lfirst(lc));
1745
appendStringInfoChar(es->str, '\n');
1748
case EXPLAIN_FORMAT_XML:
1749
ExplainXMLTag(qlabel, X_OPENING, es);
1754
appendStringInfoSpaces(es->str, es->indent * 2 + 2);
1755
appendStringInfoString(es->str, "<Item>");
1756
str = escape_xml((const char *) lfirst(lc));
1757
appendStringInfoString(es->str, str);
1759
appendStringInfoString(es->str, "</Item>\n");
1761
ExplainXMLTag(qlabel, X_CLOSING, es);
1764
case EXPLAIN_FORMAT_JSON:
1765
ExplainJSONLineEnding(es);
1766
appendStringInfoSpaces(es->str, es->indent * 2);
1767
escape_json(es->str, qlabel);
1768
appendStringInfoString(es->str, ": [");
1772
appendStringInfoString(es->str, ", ");
1773
escape_json(es->str, (const char *) lfirst(lc));
1776
appendStringInfoChar(es->str, ']');
1779
case EXPLAIN_FORMAT_YAML:
1780
ExplainYAMLLineStarting(es);
1781
appendStringInfo(es->str, "%s: ", qlabel);
1784
appendStringInfoChar(es->str, '\n');
1785
appendStringInfoSpaces(es->str, es->indent * 2 + 2);
1786
appendStringInfoString(es->str, "- ");
1787
escape_yaml(es->str, (const char *) lfirst(lc));
1794
* Explain a simple property.
1796
* If "numeric" is true, the value is a number (or other value that
1797
* doesn't need quoting in JSON).
1799
* This usually should not be invoked directly, but via one of the datatype
1800
* specific routines ExplainPropertyText, ExplainPropertyInteger, etc.
1803
ExplainProperty(const char *qlabel, const char *value, bool numeric,
1808
case EXPLAIN_FORMAT_TEXT:
1809
appendStringInfoSpaces(es->str, es->indent * 2);
1810
appendStringInfo(es->str, "%s: %s\n", qlabel, value);
1813
case EXPLAIN_FORMAT_XML:
1817
appendStringInfoSpaces(es->str, es->indent * 2);
1818
ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es);
1819
str = escape_xml(value);
1820
appendStringInfoString(es->str, str);
1822
ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);
1823
appendStringInfoChar(es->str, '\n');
1827
case EXPLAIN_FORMAT_JSON:
1828
ExplainJSONLineEnding(es);
1829
appendStringInfoSpaces(es->str, es->indent * 2);
1830
escape_json(es->str, qlabel);
1831
appendStringInfoString(es->str, ": ");
1833
appendStringInfoString(es->str, value);
1835
escape_json(es->str, value);
1838
case EXPLAIN_FORMAT_YAML:
1839
ExplainYAMLLineStarting(es);
1840
appendStringInfo(es->str, "%s: ", qlabel);
1842
appendStringInfoString(es->str, value);
1844
escape_yaml(es->str, value);
1850
* Explain a string-valued property.
1853
ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
1855
ExplainProperty(qlabel, value, false, es);
1859
* Explain an integer-valued property.
1862
ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es)
1866
snprintf(buf, sizeof(buf), "%d", value);
1867
ExplainProperty(qlabel, buf, true, es);
1871
* Explain a long-integer-valued property.
1874
ExplainPropertyLong(const char *qlabel, long value, ExplainState *es)
1878
snprintf(buf, sizeof(buf), "%ld", value);
1879
ExplainProperty(qlabel, buf, true, es);
1883
* Explain a float-valued property, using the specified number of
1884
* fractional digits.
1887
ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
1892
snprintf(buf, sizeof(buf), "%.*f", ndigits, value);
1893
ExplainProperty(qlabel, buf, true, es);
1897
* Open a group of related objects.
1899
* objtype is the type of the group object, labelname is its label within
1900
* a containing object (if any).
1902
* If labeled is true, the group members will be labeled properties,
1903
* while if it's false, they'll be unlabeled objects.
1906
ExplainOpenGroup(const char *objtype, const char *labelname,
1907
bool labeled, ExplainState *es)
1911
case EXPLAIN_FORMAT_TEXT:
1915
case EXPLAIN_FORMAT_XML:
1916
ExplainXMLTag(objtype, X_OPENING, es);
1920
case EXPLAIN_FORMAT_JSON:
1921
ExplainJSONLineEnding(es);
1922
appendStringInfoSpaces(es->str, 2 * es->indent);
1925
escape_json(es->str, labelname);
1926
appendStringInfoString(es->str, ": ");
1928
appendStringInfoChar(es->str, labeled ? '{' : '[');
1931
* In JSON format, the grouping_stack is an integer list. 0 means
1932
* we've emitted nothing at this grouping level, 1 means we've
1933
* emitted something (and so the next item needs a comma). See
1934
* ExplainJSONLineEnding().
1936
es->grouping_stack = lcons_int(0, es->grouping_stack);
1940
case EXPLAIN_FORMAT_YAML:
1943
* In YAML format, the grouping stack is an integer list. 0 means
1944
* we've emitted nothing at this grouping level AND this grouping
1945
* level is unlabelled and must be marked with "- ". See
1946
* ExplainYAMLLineStarting().
1948
ExplainYAMLLineStarting(es);
1951
appendStringInfo(es->str, "%s: ", labelname);
1952
es->grouping_stack = lcons_int(1, es->grouping_stack);
1956
appendStringInfoString(es->str, "- ");
1957
es->grouping_stack = lcons_int(0, es->grouping_stack);
1965
* Close a group of related objects.
1966
* Parameters must match the corresponding ExplainOpenGroup call.
1969
ExplainCloseGroup(const char *objtype, const char *labelname,
1970
bool labeled, ExplainState *es)
1974
case EXPLAIN_FORMAT_TEXT:
1978
case EXPLAIN_FORMAT_XML:
1980
ExplainXMLTag(objtype, X_CLOSING, es);
1983
case EXPLAIN_FORMAT_JSON:
1985
appendStringInfoChar(es->str, '\n');
1986
appendStringInfoSpaces(es->str, 2 * es->indent);
1987
appendStringInfoChar(es->str, labeled ? '}' : ']');
1988
es->grouping_stack = list_delete_first(es->grouping_stack);
1991
case EXPLAIN_FORMAT_YAML:
1993
es->grouping_stack = list_delete_first(es->grouping_stack);
1999
* Emit a "dummy" group that never has any members.
2001
* objtype is the type of the group object, labelname is its label within
2002
* a containing object (if any).
2005
ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
2009
case EXPLAIN_FORMAT_TEXT:
2013
case EXPLAIN_FORMAT_XML:
2014
ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es);
2017
case EXPLAIN_FORMAT_JSON:
2018
ExplainJSONLineEnding(es);
2019
appendStringInfoSpaces(es->str, 2 * es->indent);
2022
escape_json(es->str, labelname);
2023
appendStringInfoString(es->str, ": ");
2025
escape_json(es->str, objtype);
2028
case EXPLAIN_FORMAT_YAML:
2029
ExplainYAMLLineStarting(es);
2032
escape_yaml(es->str, labelname);
2033
appendStringInfoString(es->str, ": ");
2037
appendStringInfoString(es->str, "- ");
2039
escape_yaml(es->str, objtype);
2045
* Emit the start-of-output boilerplate.
2047
* This is just enough different from processing a subgroup that we need
2048
* a separate pair of subroutines.
2051
ExplainBeginOutput(ExplainState *es)
2055
case EXPLAIN_FORMAT_TEXT:
2059
case EXPLAIN_FORMAT_XML:
2060
appendStringInfoString(es->str,
2061
"<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
2065
case EXPLAIN_FORMAT_JSON:
2066
/* top-level structure is an array of plans */
2067
appendStringInfoChar(es->str, '[');
2068
es->grouping_stack = lcons_int(0, es->grouping_stack);
2072
case EXPLAIN_FORMAT_YAML:
2073
es->grouping_stack = lcons_int(0, es->grouping_stack);
2079
* Emit the end-of-output boilerplate.
2082
ExplainEndOutput(ExplainState *es)
2086
case EXPLAIN_FORMAT_TEXT:
2090
case EXPLAIN_FORMAT_XML:
2092
appendStringInfoString(es->str, "</explain>");
2095
case EXPLAIN_FORMAT_JSON:
2097
appendStringInfoString(es->str, "\n]");
2098
es->grouping_stack = list_delete_first(es->grouping_stack);
2101
case EXPLAIN_FORMAT_YAML:
2102
es->grouping_stack = list_delete_first(es->grouping_stack);
2108
* Put an appropriate separator between multiple plans
2111
ExplainSeparatePlans(ExplainState *es)
2115
case EXPLAIN_FORMAT_TEXT:
2116
/* add a blank line */
2117
appendStringInfoChar(es->str, '\n');
2120
case EXPLAIN_FORMAT_XML:
2121
case EXPLAIN_FORMAT_JSON:
2122
case EXPLAIN_FORMAT_YAML:
2129
* Emit opening or closing XML tag.
2131
* "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE.
2132
* Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally
2135
* XML tag names can't contain white space, so we replace any spaces in
2136
* "tagname" with dashes.
2139
ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
2143
if ((flags & X_NOWHITESPACE) == 0)
2144
appendStringInfoSpaces(es->str, 2 * es->indent);
2145
appendStringInfoCharMacro(es->str, '<');
2146
if ((flags & X_CLOSING) != 0)
2147
appendStringInfoCharMacro(es->str, '/');
2148
for (s = tagname; *s; s++)
2149
appendStringInfoCharMacro(es->str, (*s == ' ') ? '-' : *s);
2150
if ((flags & X_CLOSE_IMMEDIATE) != 0)
2151
appendStringInfoString(es->str, " /");
2152
appendStringInfoCharMacro(es->str, '>');
2153
if ((flags & X_NOWHITESPACE) == 0)
2154
appendStringInfoCharMacro(es->str, '\n');
2158
* Emit a JSON line ending.
2160
* JSON requires a comma after each property but the last. To facilitate this,
2161
* in JSON format, the text emitted for each property begins just prior to the
2162
* preceding line-break (and comma, if applicable).
2165
ExplainJSONLineEnding(ExplainState *es)
2167
Assert(es->format == EXPLAIN_FORMAT_JSON);
2168
if (linitial_int(es->grouping_stack) != 0)
2169
appendStringInfoChar(es->str, ',');
2171
linitial_int(es->grouping_stack) = 1;
2172
appendStringInfoChar(es->str, '\n');
2176
* Indent a YAML line.
2178
* YAML lines are ordinarily indented by two spaces per indentation level.
2179
* The text emitted for each property begins just prior to the preceding
2180
* line-break, except for the first property in an unlabelled group, for which
2181
* it begins immediately after the "- " that introduces the group. The first
2182
* property of the group appears on the same line as the opening "- ".
2185
ExplainYAMLLineStarting(ExplainState *es)
2187
Assert(es->format == EXPLAIN_FORMAT_YAML);
2188
if (linitial_int(es->grouping_stack) == 0)
2190
linitial_int(es->grouping_stack) = 1;
2194
appendStringInfoChar(es->str, '\n');
2195
appendStringInfoSpaces(es->str, es->indent * 2);
2200
* Produce a JSON string literal, properly escaping characters in the text.
2203
escape_json(StringInfo buf, const char *str)
2207
appendStringInfoCharMacro(buf, '\"');
2208
for (p = str; *p; p++)
2213
appendStringInfoString(buf, "\\b");
2216
appendStringInfoString(buf, "\\f");
2219
appendStringInfoString(buf, "\\n");
2222
appendStringInfoString(buf, "\\r");
2225
appendStringInfoString(buf, "\\t");
2228
appendStringInfoString(buf, "\\\"");
2231
appendStringInfoString(buf, "\\\\");
2234
if ((unsigned char) *p < ' ')
2235
appendStringInfo(buf, "\\u%04x", (int) *p);
2237
appendStringInfoCharMacro(buf, *p);
2241
appendStringInfoCharMacro(buf, '\"');
2245
* YAML is a superset of JSON; unfortuantely, the YAML quoting rules are
2246
* ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of
2247
* http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything.
2248
* Empty strings, strings with leading or trailing whitespace, and strings
2249
* containing a variety of special characters must certainly be quoted or the
2250
* output is invalid; and other seemingly harmless strings like "0xa" or
2251
* "true" must be quoted, lest they be interpreted as a hexadecimal or Boolean
2252
* constant rather than a string.
2255
escape_yaml(StringInfo buf, const char *str)
2257
escape_json(buf, str);