简介
adds the ability to show buffer-usage statistics for a query
在explain执行计划中使用buffer选项,在执行计划中添加缓冲区使用统计信息。
EXPLAIN基础
查询计划的结构是一个计划结点的树。最底层的结点是扫描结点:它们从表中返回未经处理的行。不同的表访问模式有不同的扫描结点类型:顺序扫描、索引扫描、位图索引扫描。也还有不是表的行来源。EXPLAIN给计划树中每个结点都输出一行,显示基本的结点类型和计划器为该计划结点的执行所做的开销估计。第一行(最上层的结点)是对该计划的总执行开销的估计。
explain可添加选项

举例说明
EXPLAIN有一个BUFFERS选项可以和ANALYZE一起使用来得到更多运行时统计信息,BUFFERS提供的数字帮助我们标识查询的哪些部分是对 I/O 最敏感的。
postgres=# create table user_infopostgres-# (userid int,postgres(# name text,postgres(# birthday date,postgres(# crt_time timestamp without time zonepostgres(# );CREATE TABLEpostgres=# insert into user_info (userid,name,birthday,crt_time) select generate_series(1,100000),'abcdef','2015-08-10',clock_timestamp();INSERT 0 100000不加buffers选项postgres=# explain(analyze,verbose,costs,timing) select * from user_info where userid=520;QUERY PLAN--------------------------------------------------------------------------------------------------------------Seq Scan on public.user_info (cost=0.00..1887.00 rows=1 width=23) (actual time=0.059..6.703 rows=1 loops=1)Output: userid, name, birthday, crt_timeFilter: (user_info.userid = 520)Rows Removed by Filter: 99999Planning Time: 0.041 msExecution Time: 6.717 ms(6 rows)添加buffers选项postgres=# explain(analyze,verbose,costs,buffers,timing) select * from user_info where userid=520;QUERY PLAN--------------------------------------------------------------------------------------------------------------Seq Scan on public.user_info (cost=0.00..1887.00 rows=1 width=23) (actual time=0.068..5.683 rows=1 loops=1)Output: userid, name, birthday, crt_timeFilter: (user_info.userid = 520)Rows Removed by Filter: 99999Buffers: shared hit=637 ### 数据缓冲区命中Planning Time: 23.654 msExecution Time: 5.753 ms(7 rows)postgres=# select max(ctid) from user_info;max-----------(636,148) #整个表存放在636个Page中和Buffers: shared hit=637对应(1 row)
源码导读
src/include/commands/explain.hsrc/backend/commands/explain.c/**执行计划输出结果的结构体*/typedef struct ExplainState{StringInfo str; * output buffer */* options */bool verbose; * be verbose */bool analyze; * print actual times */bool costs; * print estimated costs */bool buffers; * 打印缓冲区使用情况 */bool timing; * print detailed node timing */bool summary; * print total planning and execution timing */bool settings; * print modified settings */ExplainFormat format; * output format */* state for output formatting --- not reset for each new plan tree */int indent; * current indentation level */List *grouping_stack; * format-specific grouping state */* state related to the current plan tree (filled by ExplainPrintPlan) */PlannedStmt *pstmt; * top of plan */List *rtable; * range table */List *rtable_names; * alias names for RTEs */List *deparse_cxt; * context list for deparsing expressions */Bitmapset *printed_subplans; * ids of SubPlans we've printed */} ExplainState;
/*-------------------------------------------------------------------------** explain.c* Explain query execution plans** Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group* Portions Copyright (c) 1994-5, Regents of the University of California** IDENTIFICATION* src/backend/commands/explain.c**-------------------------------------------------------------------------*/#include "postgres.h"#include "access/xact.h"#include "catalog/pg_type.h"#include "commands/createas.h"#include "commands/defrem.h"#include "commands/prepare.h"#include "executor/nodeHash.h"#include "foreign/fdwapi.h"#include "jit/jit.h"#include "nodes/extensible.h"#include "nodes/makefuncs.h"#include "nodes/nodeFuncs.h"#include "parser/parsetree.h"#include "rewrite/rewriteHandler.h"#include "storage/bufmgr.h"#include "tcop/tcopprot.h"#include "utils/builtins.h"#include "utils/guc_tables.h"#include "utils/json.h"#include "utils/lsyscache.h"#include "utils/rel.h"#include "utils/ruleutils.h"#include "utils/snapmgr.h"#include "utils/tuplesort.h"#include "utils/typcache.h"#include "utils/xml.h"/* Hook for plugins to get control in ExplainOneQuery() */ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;/* Hook for plugins to get control in explain_get_index_name() */explain_get_index_name_hook_type explain_get_index_name_hook = NULL;/* OR-able flags for ExplainXMLTag() */#define X_OPENING 0#define X_CLOSING 1#define X_CLOSE_IMMEDIATE 2#define X_NOWHITESPACE 4static void ExplainOneQuery(Query *query, int cursorOptions,IntoClause *into, ExplainState *es,const char *queryString, ParamListInfo params,QueryEnvironment *queryEnv);static void report_triggers(ResultRelInfo *rInfo, bool show_relname,ExplainState *es);static double elapsed_time(instr_time *starttime);static bool ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used);static void ExplainNode(PlanState *planstate, List *ancestors,const char *relationship, const char *plan_name,ExplainState *es);static void show_plan_tlist(PlanState *planstate, List *ancestors,ExplainState *es);static void show_expression(Node *node, const char *qlabel,PlanState *planstate, List *ancestors,bool useprefix, ExplainState *es);static void show_qual(List *qual, const char *qlabel,PlanState *planstate, List *ancestors,bool useprefix, ExplainState *es);static void show_scan_qual(List *qual, const char *qlabel,PlanState *planstate, List *ancestors,ExplainState *es);static void show_upper_qual(List *qual, const char *qlabel,PlanState *planstate, List *ancestors,ExplainState *es);static void show_sort_keys(SortState *sortstate, List *ancestors,ExplainState *es);static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,ExplainState *es);static void show_agg_keys(AggState *astate, List *ancestors,ExplainState *es);static void show_grouping_sets(PlanState *planstate, Agg *agg,List *ancestors, ExplainState *es);static void show_grouping_set_keys(PlanState *planstate,Agg *aggnode, Sort *sortnode,List *context, bool useprefix,List *ancestors, ExplainState *es);static void show_group_keys(GroupState *gstate, List *ancestors,ExplainState *es);static void show_sort_group_keys(PlanState *planstate, const char *qlabel,int nkeys, AttrNumber *keycols,Oid *sortOperators, Oid *collations, bool *nullsFirst,List *ancestors, ExplainState *es);static void show_sortorder_options(StringInfo buf, Node *sortexpr,Oid sortOperator, Oid collation, bool nullsFirst);static void show_tablesample(TableSampleClause *tsc, PlanState *planstate,List *ancestors, ExplainState *es);static void show_sort_info(SortState *sortstate, ExplainState *es);static void show_hash_info(HashState *hashstate, ExplainState *es);static void show_tidbitmap_info(BitmapHeapScanState *planstate,ExplainState *es);static void show_instrumentation_count(const char *qlabel, int which,PlanState *planstate, ExplainState *es);static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);static void show_eval_params(Bitmapset *bms_params, ExplainState *es);static const char *explain_get_index_name(Oid indexId);static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,ExplainState *es);static void ExplainScanTarget(Scan *plan, ExplainState *es);static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,ExplainState *es);static void ExplainMemberNodes(PlanState **planstates, int nplans,List *ancestors, ExplainState *es);static void ExplainMissingMembers(int nplans, int nchildren, ExplainState *es);static void ExplainSubPlans(List *plans, List *ancestors,const char *relationship, ExplainState *es);static void ExplainCustomChildren(CustomScanState *css,List *ancestors, ExplainState *es);static void ExplainProperty(const char *qlabel, const char *unit,const char *value, bool numeric, ExplainState *es);static void ExplainDummyGroup(const char *objtype, const char *labelname,ExplainState *es);static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);static void ExplainJSONLineEnding(ExplainState *es);static void ExplainYAMLLineStarting(ExplainState *es);static void escape_yaml(StringInfo buf, const char *str);/** ExplainQuery -* execute an EXPLAIN command*/voidExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,ParamListInfo params, QueryEnvironment *queryEnv,DestReceiver *dest){ExplainState *es = NewExplainState();TupOutputState *tstate;List *rewritten;ListCell *lc;bool timing_set = false;bool summary_set = false;/* Parse options list. */foreach(lc, stmt->options){DefElem *opt = (DefElem *) lfirst(lc);if (strcmp(opt->defname, "analyze") == 0)es->analyze = defGetBoolean(opt);else if (strcmp(opt->defname, "verbose") == 0)es->verbose = defGetBoolean(opt);else if (strcmp(opt->defname, "costs") == 0)es->costs = defGetBoolean(opt);else if (strcmp(opt->defname, "buffers") == 0)es->buffers = defGetBoolean(opt);else if (strcmp(opt->defname, "settings") == 0)es->settings = defGetBoolean(opt);else if (strcmp(opt->defname, "timing") == 0){timing_set = true;es->timing = defGetBoolean(opt);}else if (strcmp(opt->defname, "summary") == 0){summary_set = true;es->summary = defGetBoolean(opt);}else if (strcmp(opt->defname, "format") == 0){char *p = defGetString(opt);if (strcmp(p, "text") == 0)es->format = EXPLAIN_FORMAT_TEXT;else if (strcmp(p, "xml") == 0)es->format = EXPLAIN_FORMAT_XML;else if (strcmp(p, "json") == 0)es->format = EXPLAIN_FORMAT_JSON;else if (strcmp(p, "yaml") == 0)es->format = EXPLAIN_FORMAT_YAML;elseereport(ERROR,(errcode(ERRCODE_INVALID_PARAMETER_VALUE),errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",opt->defname, p),parser_errposition(pstate, opt->location)));}elseereport(ERROR,(errcode(ERRCODE_SYNTAX_ERROR),errmsg("unrecognized EXPLAIN option \"%s\"",opt->defname),parser_errposition(pstate, opt->location)));}if (es->buffers && !es->analyze)ereport(ERROR,(errcode(ERRCODE_INVALID_PARAMETER_VALUE),errmsg("EXPLAIN option BUFFERS requires ANALYZE")));/* if the timing was not set explicitly, set default value */es->timing = (timing_set) ? es->timing : es->analyze;/* check that timing is used with EXPLAIN ANALYZE */if (es->timing && !es->analyze)ereport(ERROR,(errcode(ERRCODE_INVALID_PARAMETER_VALUE),errmsg("EXPLAIN option TIMING requires ANALYZE")));/* if the summary was not set explicitly, set default value */es->summary = (summary_set) ? es->summary : es->analyze;/** Parse analysis was done already, but we still have to run the rule* rewriter. We do not do AcquireRewriteLocks: we assume the query either* came straight from the parser, or suitable locks were acquired by* plancache.c.** Because the rewriter and planner tend to scribble on the input, we make* a preliminary copy of the source querytree. This prevents problems in* the case that the EXPLAIN is in a portal or plpgsql function and is* executed repeatedly. (See also the same hack in DECLARE CURSOR and* PREPARE.) XXX FIXME someday.*/rewritten = QueryRewrite(castNode(Query, copyObject(stmt->query)));/* emit opening boilerplate */ExplainBeginOutput(es);if (rewritten == NIL){/** In the case of an INSTEAD NOTHING, tell at least that. But in* non-text format, the output is delimited, so this isn't necessary.*/if (es->format == EXPLAIN_FORMAT_TEXT)appendStringInfoString(es->str, "Query rewrites to nothing\n");}else{ListCell *l;/* Explain every plan */foreach(l, rewritten){ExplainOneQuery(lfirst_node(Query, l),CURSOR_OPT_PARALLEL_OK, NULL, es,queryString, params, queryEnv);/* Separate plans with an appropriate separator */if (lnext(l) != NULL)ExplainSeparatePlans(es);}}/* emit closing boilerplate */ExplainEndOutput(es);Assert(es->indent == 0);/* output tuples */tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt),&TTSOpsVirtual);if (es->format == EXPLAIN_FORMAT_TEXT)do_text_output_multiline(tstate, es->str->data);elsedo_text_output_oneline(tstate, es->str->data);end_tup_output(tstate);pfree(es->str->data);}/** Create a new ExplainState struct initialized with default options.*/ExplainState *NewExplainState(void){ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));/* Set default options (most fields can be left as zeroes). */es->costs = true;/* Prepare output buffer. */es->str = makeStringInfo();return es;}/** ExplainResultDesc -* construct the result tupledesc for an EXPLAIN*/TupleDescExplainResultDesc(ExplainStmt *stmt){TupleDesc tupdesc;ListCell *lc;Oid result_type = TEXTOID;/* Check for XML format option */foreach(lc, stmt->options){DefElem *opt = (DefElem *) lfirst(lc);if (strcmp(opt->defname, "format") == 0){char *p = defGetString(opt);if (strcmp(p, "xml") == 0)result_type = XMLOID;else if (strcmp(p, "json") == 0)result_type = JSONOID;elseresult_type = TEXTOID;/* don't "break", as ExplainQuery will use the last value */}}/* Need a tuple descriptor representing a single TEXT or XML column */tupdesc = CreateTemplateTupleDesc(1);TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",result_type, -1, 0);return tupdesc;}/** ExplainOneQuery -* print out the execution plan for one Query** "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.*/static voidExplainOneQuery(Query *query, int cursorOptions,IntoClause *into, ExplainState *es,const char *queryString, ParamListInfo params,QueryEnvironment *queryEnv){/* planner will not cope with utility statements */if (query->commandType == CMD_UTILITY){ExplainOneUtility(query->utilityStmt, into, es, queryString, params,queryEnv);return;}/* if an advisor plugin is present, let it manage things */if (ExplainOneQuery_hook)(*ExplainOneQuery_hook) (query, cursorOptions, into, es,queryString, params, queryEnv);else{PlannedStmt *plan;instr_time planstart,planduration;INSTR_TIME_SET_CURRENT(planstart);/* plan the query */plan = pg_plan_query(query, cursorOptions, params);INSTR_TIME_SET_CURRENT(planduration);INSTR_TIME_SUBTRACT(planduration, planstart);/* run it (if needed) and produce output */ExplainOnePlan(plan, into, es, queryString, params, queryEnv,&planduration);}}/** ExplainOneUtility -* print out the execution plan for one utility statement* (In general, utility statements don't have plans, but there are some* we treat as special cases)** "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.** This is exported because it's called back from prepare.c in the* EXPLAIN EXECUTE case.*/voidExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,const char *queryString, ParamListInfo params,QueryEnvironment *queryEnv){if (utilityStmt == NULL)return;if (IsA(utilityStmt, CreateTableAsStmt)){/** We have to rewrite the contained SELECT and then pass it back to* ExplainOneQuery. It's probably not really necessary to copy the* contained parsetree another time, but let's be safe.*/CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;List *rewritten;rewritten = QueryRewrite(castNode(Query, copyObject(ctas->query)));Assert(list_length(rewritten) == 1);ExplainOneQuery(linitial_node(Query, rewritten),CURSOR_OPT_PARALLEL_OK, ctas->into, es,queryString, params, queryEnv);}else if (IsA(utilityStmt, DeclareCursorStmt)){/** Likewise for DECLARE CURSOR.** Notice that if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll* actually run the query. This is different from pre-8.3 behavior* but seems more useful than not running the query. No cursor will* be created, however.*/DeclareCursorStmt *dcs = (DeclareCursorStmt *) utilityStmt;List *rewritten;rewritten = QueryRewrite(castNode(Query, copyObject(dcs->query)));Assert(list_length(rewritten) == 1);ExplainOneQuery(linitial_node(Query, rewritten),dcs->options, NULL, es,queryString, params, queryEnv);}else if (IsA(utilityStmt, ExecuteStmt))ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,queryString, params, queryEnv);else if (IsA(utilityStmt, NotifyStmt)){if (es->format == EXPLAIN_FORMAT_TEXT)appendStringInfoString(es->str, "NOTIFY\n");elseExplainDummyGroup("Notify", NULL, es);}else{if (es->format == EXPLAIN_FORMAT_TEXT)appendStringInfoString(es->str,"Utility statements have no plan structure\n");elseExplainDummyGroup("Utility Statement", NULL, es);}}/** ExplainOnePlan -* given a planned query, execute it if needed, and then print* EXPLAIN output** "into" is NULL unless we are explaining the contents of a CreateTableAsStmt,* in which case executing the query should result in creating that table.** This is exported because it's called back from prepare.c in the* EXPLAIN EXECUTE case, and because an index advisor plugin would need* to call it.*/voidExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,const char *queryString, ParamListInfo params,QueryEnvironment *queryEnv, const instr_time *planduration){DestReceiver *dest;QueryDesc *queryDesc;instr_time starttime;double totaltime = 0;int eflags;int instrument_option = 0;Assert(plannedstmt->commandType != CMD_UTILITY);if (es->analyze && es->timing)instrument_option |= INSTRUMENT_TIMER;else if (es->analyze)instrument_option |= INSTRUMENT_ROWS;if (es->buffers)instrument_option |= INSTRUMENT_BUFFERS;/** We always collect timing for the entire statement, even when node-level* timing is off, so we don't look at es->timing here. (We could skip* this if !es->summary, but it's hardly worth the complication.)*/INSTR_TIME_SET_CURRENT(starttime);/** Use a snapshot with an updated command ID to ensure this query sees* results of any previously executed queries.*/PushCopiedSnapshot(GetActiveSnapshot());UpdateActiveSnapshotCommandId();/** Normally we discard the query's output, but if explaining CREATE TABLE* AS, we'd better use the appropriate tuple receiver.*/if (into)dest = CreateIntoRelDestReceiver(into);elsedest = None_Receiver;/* Create a QueryDesc for the query */queryDesc = CreateQueryDesc(plannedstmt, queryString,GetActiveSnapshot(), InvalidSnapshot,dest, params, queryEnv, instrument_option);/* Select execution options */if (es->analyze)eflags = 0; /* default run-to-completion flags */elseeflags = EXEC_FLAG_EXPLAIN_ONLY;if (into)eflags |= GetIntoRelEFlags(into);/* call ExecutorStart to prepare the plan for execution */ExecutorStart(queryDesc, eflags);/* Execute the plan for statistics if asked for */if (es->analyze){ScanDirection dir;/* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */if (into && into->skipData)dir = NoMovementScanDirection;elsedir = ForwardScanDirection;/* run the plan */ExecutorRun(queryDesc, dir, 0L, true);/* run cleanup too */ExecutorFinish(queryDesc);/* We can't run ExecutorEnd 'till we're done printing the stats... */totaltime += elapsed_time(&starttime);}ExplainOpenGroup("Query", NULL, true, es);/* Create textual dump of plan tree */ExplainPrintPlan(es, queryDesc);if (es->summary && planduration){double plantime = INSTR_TIME_GET_DOUBLE(*planduration);ExplainPropertyFloat("Planning Time", "ms", 1000.0 * plantime, 3, es);}/* Print info about runtime of triggers */if (es->analyze)ExplainPrintTriggers(es, queryDesc);/** Print info about JITing. Tied to es->costs because we don't want to* display this in regression tests, as it'd cause output differences* depending on build options. Might want to separate that out from COSTS* at a later stage.*/if (es->costs)ExplainPrintJITSummary(es, queryDesc);/** Close down the query and free resources. Include time for this in the* total execution time (although it should be pretty minimal).*/INSTR_TIME_SET_CURRENT(starttime);ExecutorEnd(queryDesc);FreeQueryDesc(queryDesc);PopActiveSnapshot();/* We need a CCI just in case query expanded to multiple plans */if (es->analyze)CommandCounterIncrement();totaltime += elapsed_time(&starttime);/** We only report execution time if we actually ran the query (that is,* the user specified ANALYZE), and if summary reporting is enabled (the* user can set SUMMARY OFF to not have the timing information included in* the output). By default, ANALYZE sets SUMMARY to true.*/if (es->summary && es->analyze)ExplainPropertyFloat("Execution Time", "ms", 1000.0 * totaltime, 3,es);ExplainCloseGroup("Query", NULL, true, es);}/** ExplainPrintSettings -* Print summary of modified settings affecting query planning.*/static voidExplainPrintSettings(ExplainState *es){int num;struct config_generic **gucs;/* bail out if information about settings not requested */if (!es->settings)return;/* request an array of relevant settings */gucs = get_explain_guc_options(&num);if (es->format != EXPLAIN_FORMAT_TEXT){ExplainOpenGroup("Settings", "Settings", true, es);for (int i = 0; i < num; i++){char *setting;struct config_generic *conf = gucs[i];setting = GetConfigOptionByName(conf->name, NULL, true);ExplainPropertyText(conf->name, setting, es);}ExplainCloseGroup("Settings", "Settings", true, es);}else{StringInfoData str;/* In TEXT mode, print nothing if there are no options */if (num <= 0)return;initStringInfo(&str);for (int i = 0; i < num; i++){char *setting;struct config_generic *conf = gucs[i];if (i > 0)appendStringInfoString(&str, ", ");setting = GetConfigOptionByName(conf->name, NULL, true);if (setting)appendStringInfo(&str, "%s = '%s'", conf->name, setting);elseappendStringInfo(&str, "%s = NULL", conf->name);}ExplainPropertyText("Settings", str.data, es);}}/** ExplainPrintPlan -* convert a QueryDesc's plan tree to text and append it to es->str** The caller should have set up the options fields of *es, as well as* initializing the output buffer es->str. Also, output formatting state* such as the indent level is assumed valid. Plan-tree-specific fields* in *es are initialized here.** NB: will not work on utility statements*/voidExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc){Bitmapset *rels_used = NULL;PlanState *ps;/* Set up ExplainState fields associated with this plan tree */Assert(queryDesc->plannedstmt != NULL);es->pstmt = queryDesc->plannedstmt;es->rtable = queryDesc->plannedstmt->rtable;ExplainPreScanNode(queryDesc->planstate, &rels_used);es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used);es->deparse_cxt = deparse_context_for_plan_rtable(es->rtable,es->rtable_names);es->printed_subplans = NULL;/** Sometimes we mark a Gather node as "invisible", which means that it's* not displayed in EXPLAIN output. The purpose of this is to allow* running regression tests with force_parallel_mode=regress to get the* same results as running the same tests with force_parallel_mode=off.*/ps = queryDesc->planstate;if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)ps = outerPlanState(ps);ExplainNode(ps, NIL, NULL, NULL, es);/** If requested, include information about GUC parameters with values that* don't match the built-in defaults.*/ExplainPrintSettings(es);}/** ExplainPrintTriggers -* convert a QueryDesc's trigger statistics to text and append it to* es->str** The caller should have set up the options fields of *es, as well as* initializing the output buffer es->str. Other fields in *es are* initialized here.*/voidExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc){ResultRelInfo *rInfo;bool show_relname;int numrels = queryDesc->estate->es_num_result_relations;int numrootrels = queryDesc->estate->es_num_root_result_relations;List *routerels;List *targrels;int nr;ListCell *l;routerels = queryDesc->estate->es_tuple_routing_result_relations;targrels = queryDesc->estate->es_trig_target_relations;ExplainOpenGroup("Triggers", "Triggers", false, es);show_relname = (numrels > 1 || numrootrels > 0 ||routerels != NIL || targrels != NIL);rInfo = queryDesc->estate->es_result_relations;for (nr = 0; nr < numrels; rInfo++, nr++)report_triggers(rInfo, show_relname, es);rInfo = queryDesc->estate->es_root_result_relations;for (nr = 0; nr < numrootrels; rInfo++, nr++)report_triggers(rInfo, show_relname, es);foreach(l, routerels){rInfo = (ResultRelInfo *) lfirst(l);report_triggers(rInfo, show_relname, es);}foreach(l, targrels){rInfo = (ResultRelInfo *) lfirst(l);report_triggers(rInfo, show_relname, es);}ExplainCloseGroup("Triggers", "Triggers", false, es);}/** ExplainPrintJITSummary -* Print summarized JIT instrumentation from leader and workers*/voidExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc){JitInstrumentation ji = {0};if (!(queryDesc->estate->es_jit_flags & PGJIT_PERFORM))return;/** Work with a copy instead of modifying the leader state, since this* function may be called twice*/if (queryDesc->estate->es_jit)InstrJitAgg(&ji, &queryDesc->estate->es_jit->instr);/* If this process has done JIT in parallel workers, merge stats */if (queryDesc->estate->es_jit_worker_instr)InstrJitAgg(&ji, queryDesc->estate->es_jit_worker_instr);ExplainPrintJIT(es, queryDesc->estate->es_jit_flags, &ji, -1);}/** ExplainPrintJIT -* Append information about JITing to es->str.** Can be used to print the JIT instrumentation of the backend (worker_num =* -1) or that of a specific worker (worker_num = ...).*/voidExplainPrintJIT(ExplainState *es, int jit_flags,JitInstrumentation *ji, int worker_num){instr_time total_time;bool for_workers = (worker_num >= 0);/* don't print information if no JITing happened */if (!ji || ji->created_functions == 0)return;/* calculate total time */INSTR_TIME_SET_ZERO(total_time);INSTR_TIME_ADD(total_time, ji->generation_counter);INSTR_TIME_ADD(total_time, ji->inlining_counter);INSTR_TIME_ADD(total_time, ji->optimization_counter);INSTR_TIME_ADD(total_time, ji->emission_counter);ExplainOpenGroup("JIT", "JIT", true, es);/* for higher density, open code the text output format */if (es->format == EXPLAIN_FORMAT_TEXT){appendStringInfoSpaces(es->str, es->indent * 2);if (for_workers)appendStringInfo(es->str, "JIT for worker %u:\n", worker_num);elseappendStringInfo(es->str, "JIT:\n");es->indent += 1;ExplainPropertyInteger("Functions", NULL, ji->created_functions, es);appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfo(es->str, "Options: %s %s, %s %s, %s %s, %s %s\n","Inlining", jit_flags & PGJIT_INLINE ? "true" : "false","Optimization", jit_flags & PGJIT_OPT3 ? "true" : "false","Expressions", jit_flags & PGJIT_EXPR ? "true" : "false","Deforming", jit_flags & PGJIT_DEFORM ? "true" : "false");if (es->analyze && es->timing){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfo(es->str,"Timing: %s %.3f ms, %s %.3f ms, %s %.3f ms, %s %.3f ms, %s %.3f ms\n","Generation", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->generation_counter),"Inlining", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->inlining_counter),"Optimization", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->optimization_counter),"Emission", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->emission_counter),"Total", 1000.0 * INSTR_TIME_GET_DOUBLE(total_time));}es->indent -= 1;}else{ExplainPropertyInteger("Worker Number", NULL, worker_num, es);ExplainPropertyInteger("Functions", NULL, ji->created_functions, es);ExplainOpenGroup("Options", "Options", true, es);ExplainPropertyBool("Inlining", jit_flags & PGJIT_INLINE, es);ExplainPropertyBool("Optimization", jit_flags & PGJIT_OPT3, es);ExplainPropertyBool("Expressions", jit_flags & PGJIT_EXPR, es);ExplainPropertyBool("Deforming", jit_flags & PGJIT_DEFORM, es);ExplainCloseGroup("Options", "Options", true, es);if (es->analyze && es->timing){ExplainOpenGroup("Timing", "Timing", true, es);ExplainPropertyFloat("Generation", "ms",1000.0 * INSTR_TIME_GET_DOUBLE(ji->generation_counter),3, es);ExplainPropertyFloat("Inlining", "ms",1000.0 * INSTR_TIME_GET_DOUBLE(ji->inlining_counter),3, es);ExplainPropertyFloat("Optimization", "ms",1000.0 * INSTR_TIME_GET_DOUBLE(ji->optimization_counter),3, es);ExplainPropertyFloat("Emission", "ms",1000.0 * INSTR_TIME_GET_DOUBLE(ji->emission_counter),3, es);ExplainPropertyFloat("Total", "ms",1000.0 * INSTR_TIME_GET_DOUBLE(total_time),3, es);ExplainCloseGroup("Timing", "Timing", true, es);}}ExplainCloseGroup("JIT", "JIT", true, es);}/** ExplainQueryText -* add a "Query Text" node that contains the actual text of the query** The caller should have set up the options fields of *es, as well as* initializing the output buffer es->str.**/voidExplainQueryText(ExplainState *es, QueryDesc *queryDesc){if (queryDesc->sourceText)ExplainPropertyText("Query Text", queryDesc->sourceText, es);}/** report_triggers -* report execution stats for a single relation's triggers*/static voidreport_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es){int nt;if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)return;for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++){Trigger *trig = rInfo->ri_TrigDesc->triggers + nt;Instrumentation *instr = rInfo->ri_TrigInstrument + nt;char *relname;char *conname = NULL;/* Must clean up instrumentation state */InstrEndLoop(instr);/** We ignore triggers that were never invoked; they likely aren't* relevant to the current query type.*/if (instr->ntuples == 0)continue;ExplainOpenGroup("Trigger", NULL, true, es);relname = RelationGetRelationName(rInfo->ri_RelationDesc);if (OidIsValid(trig->tgconstraint))conname = get_constraint_name(trig->tgconstraint);/** In text format, we avoid printing both the trigger name and the* constraint name unless VERBOSE is specified. In non-text formats* we just print everything.*/if (es->format == EXPLAIN_FORMAT_TEXT){if (es->verbose || conname == NULL)appendStringInfo(es->str, "Trigger %s", trig->tgname);elseappendStringInfoString(es->str, "Trigger");if (conname)appendStringInfo(es->str, " for constraint %s", conname);if (show_relname)appendStringInfo(es->str, " on %s", relname);if (es->timing)appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",1000.0 * instr->total, instr->ntuples);elseappendStringInfo(es->str, ": calls=%.0f\n", instr->ntuples);}else{ExplainPropertyText("Trigger Name", trig->tgname, es);if (conname)ExplainPropertyText("Constraint Name", conname, es);ExplainPropertyText("Relation", relname, es);if (es->timing)ExplainPropertyFloat("Time", "ms", 1000.0 * instr->total, 3,es);ExplainPropertyFloat("Calls", NULL, instr->ntuples, 0, es);}if (conname)pfree(conname);ExplainCloseGroup("Trigger", NULL, true, es);}}/* Compute elapsed time in seconds since given timestamp */static doubleelapsed_time(instr_time *starttime){instr_time endtime;INSTR_TIME_SET_CURRENT(endtime);INSTR_TIME_SUBTRACT(endtime, *starttime);return INSTR_TIME_GET_DOUBLE(endtime);}/** ExplainPreScanNode -* Prescan the planstate tree to identify which RTEs are referenced** Adds the relid of each referenced RTE to *rels_used. The result controls* which RTEs are assigned aliases by select_rtable_names_for_explain.* This ensures that we don't confusingly assign un-suffixed aliases to RTEs* that never appear in the EXPLAIN output (such as inheritance parents).*/static boolExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used){Plan *plan = planstate->plan;switch (nodeTag(plan)){case T_SeqScan:case T_SampleScan:case T_IndexScan:case T_IndexOnlyScan:case T_BitmapHeapScan:case T_TidScan:case T_SubqueryScan:case T_FunctionScan:case T_TableFuncScan:case T_ValuesScan:case T_CteScan:case T_NamedTuplestoreScan:case T_WorkTableScan:*rels_used = bms_add_member(*rels_used,((Scan *) plan)->scanrelid);break;case T_ForeignScan:*rels_used = bms_add_members(*rels_used,((ForeignScan *) plan)->fs_relids);break;case T_CustomScan:*rels_used = bms_add_members(*rels_used,((CustomScan *) plan)->custom_relids);break;case T_ModifyTable:*rels_used = bms_add_member(*rels_used,((ModifyTable *) plan)->nominalRelation);if (((ModifyTable *) plan)->exclRelRTI)*rels_used = bms_add_member(*rels_used,((ModifyTable *) plan)->exclRelRTI);break;default:break;}return planstate_tree_walker(planstate, ExplainPreScanNode, rels_used);}/** ExplainNode -* Appends a description of a plan tree to es->str** planstate points to the executor state node for the current plan node.* We need to work from a PlanState node, not just a Plan node, in order to* get at the instrumentation data (if any) as well as the list of subplans.** ancestors is a list of parent PlanState nodes, most-closely-nested first.* These are needed in order to interpret PARAM_EXEC Params.** relationship describes the relationship of this plan node to its parent* (eg, "Outer", "Inner"); it can be null at top level. plan_name is an* optional name to be attached to the node.** In text format, es->indent is controlled in this function since we only* want it to change at plan-node boundaries. In non-text formats, es->indent* corresponds to the nesting depth of logical output groups, and therefore* is controlled by ExplainOpenGroup/ExplainCloseGroup.*/static voidExplainNode(PlanState *planstate, List *ancestors,const char *relationship, const char *plan_name,ExplainState *es){Plan *plan = planstate->plan;const char *pname; /* node type name for text output */const char *sname; /* node type name for non-text output */const char *strategy = NULL;const char *partialmode = NULL;const char *operation = NULL;const char *custom_name = NULL;int save_indent = es->indent;bool haschildren;switch (nodeTag(plan)){case T_Result:pname = sname = "Result";break;case T_ProjectSet:pname = sname = "ProjectSet";break;case T_ModifyTable:sname = "ModifyTable";switch (((ModifyTable *) plan)->operation){case CMD_INSERT:pname = operation = "Insert";break;case CMD_UPDATE:pname = operation = "Update";break;case CMD_DELETE:pname = operation = "Delete";break;default:pname = "???";break;}break;case T_Append:pname = sname = "Append";break;case T_MergeAppend:pname = sname = "Merge Append";break;case T_RecursiveUnion:pname = sname = "Recursive Union";break;case T_BitmapAnd:pname = sname = "BitmapAnd";break;case T_BitmapOr:pname = sname = "BitmapOr";break;case T_NestLoop:pname = sname = "Nested Loop";break;case T_MergeJoin:pname = "Merge"; /* "Join" gets added by jointype switch */sname = "Merge Join";break;case T_HashJoin:pname = "Hash"; /* "Join" gets added by jointype switch */sname = "Hash Join";break;case T_SeqScan:pname = sname = "Seq Scan";break;case T_SampleScan:pname = sname = "Sample Scan";break;case T_Gather:pname = sname = "Gather";break;case T_GatherMerge:pname = sname = "Gather Merge";break;case T_IndexScan:pname = sname = "Index Scan";break;case T_IndexOnlyScan:pname = sname = "Index Only Scan";break;case T_BitmapIndexScan:pname = sname = "Bitmap Index Scan";break;case T_BitmapHeapScan:pname = sname = "Bitmap Heap Scan";break;case T_TidScan:pname = sname = "Tid Scan";break;case T_SubqueryScan:pname = sname = "Subquery Scan";break;case T_FunctionScan:pname = sname = "Function Scan";break;case T_TableFuncScan:pname = sname = "Table Function Scan";break;case T_ValuesScan:pname = sname = "Values Scan";break;case T_CteScan:pname = sname = "CTE Scan";break;case T_NamedTuplestoreScan:pname = sname = "Named Tuplestore Scan";break;case T_WorkTableScan:pname = sname = "WorkTable Scan";break;case T_ForeignScan:sname = "Foreign Scan";switch (((ForeignScan *) plan)->operation){case CMD_SELECT:pname = "Foreign Scan";operation = "Select";break;case CMD_INSERT:pname = "Foreign Insert";operation = "Insert";break;case CMD_UPDATE:pname = "Foreign Update";operation = "Update";break;case CMD_DELETE:pname = "Foreign Delete";operation = "Delete";break;default:pname = "???";break;}break;case T_CustomScan:sname = "Custom Scan";custom_name = ((CustomScan *) plan)->methods->CustomName;if (custom_name)pname = psprintf("Custom Scan (%s)", custom_name);elsepname = sname;break;case T_Material:pname = sname = "Materialize";break;case T_Sort:pname = sname = "Sort";break;case T_Group:pname = sname = "Group";break;case T_Agg:{Agg *agg = (Agg *) plan;sname = "Aggregate";switch (agg->aggstrategy){case AGG_PLAIN:pname = "Aggregate";strategy = "Plain";break;case AGG_SORTED:pname = "GroupAggregate";strategy = "Sorted";break;case AGG_HASHED:pname = "HashAggregate";strategy = "Hashed";break;case AGG_MIXED:pname = "MixedAggregate";strategy = "Mixed";break;default:pname = "Aggregate ???";strategy = "???";break;}if (DO_AGGSPLIT_SKIPFINAL(agg->aggsplit)){partialmode = "Partial";pname = psprintf("%s %s", partialmode, pname);}else if (DO_AGGSPLIT_COMBINE(agg->aggsplit)){partialmode = "Finalize";pname = psprintf("%s %s", partialmode, pname);}elsepartialmode = "Simple";}break;case T_WindowAgg:pname = sname = "WindowAgg";break;case T_Unique:pname = sname = "Unique";break;case T_SetOp:sname = "SetOp";switch (((SetOp *) plan)->strategy){case SETOP_SORTED:pname = "SetOp";strategy = "Sorted";break;case SETOP_HASHED:pname = "HashSetOp";strategy = "Hashed";break;default:pname = "SetOp ???";strategy = "???";break;}break;case T_LockRows:pname = sname = "LockRows";break;case T_Limit:pname = sname = "Limit";break;case T_Hash:pname = sname = "Hash";break;default:pname = sname = "???";break;}ExplainOpenGroup("Plan",relationship ? NULL : "Plan",true, es);if (es->format == EXPLAIN_FORMAT_TEXT){if (plan_name){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfo(es->str, "%s\n", plan_name);es->indent++;}if (es->indent){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfoString(es->str, "-> ");es->indent += 2;}if (plan->parallel_aware)appendStringInfoString(es->str, "Parallel ");appendStringInfoString(es->str, pname);es->indent++;}else{ExplainPropertyText("Node Type", sname, es);if (strategy)ExplainPropertyText("Strategy", strategy, es);if (partialmode)ExplainPropertyText("Partial Mode", partialmode, es);if (operation)ExplainPropertyText("Operation", operation, es);if (relationship)ExplainPropertyText("Parent Relationship", relationship, es);if (plan_name)ExplainPropertyText("Subplan Name", plan_name, es);if (custom_name)ExplainPropertyText("Custom Plan Provider", custom_name, es);ExplainPropertyBool("Parallel Aware", plan->parallel_aware, es);}switch (nodeTag(plan)){case T_SeqScan:case T_SampleScan:case T_BitmapHeapScan:case T_TidScan:case T_SubqueryScan:case T_FunctionScan:case T_TableFuncScan:case T_ValuesScan:case T_CteScan:case T_WorkTableScan:ExplainScanTarget((Scan *) plan, es);break;case T_ForeignScan:case T_CustomScan:if (((Scan *) plan)->scanrelid > 0)ExplainScanTarget((Scan *) plan, es);break;case T_IndexScan:{IndexScan *indexscan = (IndexScan *) plan;ExplainIndexScanDetails(indexscan->indexid,indexscan->indexorderdir,es);ExplainScanTarget((Scan *) indexscan, es);}break;case T_IndexOnlyScan:{IndexOnlyScan *indexonlyscan = (IndexOnlyScan *) plan;ExplainIndexScanDetails(indexonlyscan->indexid,indexonlyscan->indexorderdir,es);ExplainScanTarget((Scan *) indexonlyscan, es);}break;case T_BitmapIndexScan:{BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;const char *indexname =explain_get_index_name(bitmapindexscan->indexid);if (es->format == EXPLAIN_FORMAT_TEXT)appendStringInfo(es->str, " on %s",quote_identifier(indexname));elseExplainPropertyText("Index Name", indexname, es);}break;case T_ModifyTable:ExplainModifyTarget((ModifyTable *) plan, es);break;case T_NestLoop:case T_MergeJoin:case T_HashJoin:{const char *jointype;switch (((Join *) plan)->jointype){case JOIN_INNER:jointype = "Inner";break;case JOIN_LEFT:jointype = "Left";break;case JOIN_FULL:jointype = "Full";break;case JOIN_RIGHT:jointype = "Right";break;case JOIN_SEMI:jointype = "Semi";break;case JOIN_ANTI:jointype = "Anti";break;default:jointype = "???";break;}if (es->format == EXPLAIN_FORMAT_TEXT){/** For historical reasons, the join type is interpolated* into the node type name...*/if (((Join *) plan)->jointype != JOIN_INNER)appendStringInfo(es->str, " %s Join", jointype);else if (!IsA(plan, NestLoop))appendStringInfoString(es->str, " Join");}elseExplainPropertyText("Join Type", jointype, es);}break;case T_SetOp:{const char *setopcmd;switch (((SetOp *) plan)->cmd){case SETOPCMD_INTERSECT:setopcmd = "Intersect";break;case SETOPCMD_INTERSECT_ALL:setopcmd = "Intersect All";break;case SETOPCMD_EXCEPT:setopcmd = "Except";break;case SETOPCMD_EXCEPT_ALL:setopcmd = "Except All";break;default:setopcmd = "???";break;}if (es->format == EXPLAIN_FORMAT_TEXT)appendStringInfo(es->str, " %s", setopcmd);elseExplainPropertyText("Command", setopcmd, es);}break;default:break;}if (es->costs){if (es->format == EXPLAIN_FORMAT_TEXT){appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)",plan->startup_cost, plan->total_cost,plan->plan_rows, plan->plan_width);}else{ExplainPropertyFloat("Startup Cost", NULL, plan->startup_cost,2, es);ExplainPropertyFloat("Total Cost", NULL, plan->total_cost,2, es);ExplainPropertyFloat("Plan Rows", NULL, plan->plan_rows,0, es);ExplainPropertyInteger("Plan Width", NULL, plan->plan_width,es);}}/** We have to forcibly clean up the instrumentation state because we* haven't done ExecutorEnd yet. This is pretty grotty ...** Note: contrib/auto_explain could cause instrumentation to be set up* even though we didn't ask for it here. Be careful not to print any* instrumentation results the user didn't ask for. But we do the* InstrEndLoop call anyway, if possible, to reduce the number of cases* auto_explain has to contend with.*/if (planstate->instrument)InstrEndLoop(planstate->instrument);if (es->analyze &&planstate->instrument && planstate->instrument->nloops > 0){double nloops = planstate->instrument->nloops;double startup_ms = 1000.0 * planstate->instrument->startup nloops;double total_ms = 1000.0 * planstate->instrument->total nloops;double rows = planstate->instrument->ntuples nloops;if (es->format == EXPLAIN_FORMAT_TEXT){if (es->timing)appendStringInfo(es->str," (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",startup_ms, total_ms, rows, nloops);elseappendStringInfo(es->str," (actual rows=%.0f loops=%.0f)",rows, nloops);}else{if (es->timing){ExplainPropertyFloat("Actual Startup Time", "s", startup_ms,3, es);ExplainPropertyFloat("Actual Total Time", "s", total_ms,3, es);}ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es);ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es);}}else if (es->analyze){if (es->format == EXPLAIN_FORMAT_TEXT)appendStringInfoString(es->str, " (never executed)");else{if (es->timing){ExplainPropertyFloat("Actual Startup Time", "ms", 0.0, 3, es);ExplainPropertyFloat("Actual Total Time", "ms", 0.0, 3, es);}ExplainPropertyFloat("Actual Rows", NULL, 0.0, 0, es);ExplainPropertyFloat("Actual Loops", NULL, 0.0, 0, es);}}/* in text format, first line ends here */if (es->format == EXPLAIN_FORMAT_TEXT)appendStringInfoChar(es->str, '\n');/* target list */if (es->verbose)show_plan_tlist(planstate, ancestors, es);/* unique join */switch (nodeTag(plan)){case T_NestLoop:case T_MergeJoin:case T_HashJoin:/* try not to be too chatty about this in text mode */if (es->format != EXPLAIN_FORMAT_TEXT ||(es->verbose && ((Join *) plan)->inner_unique))ExplainPropertyBool("Inner Unique",((Join *) plan)->inner_unique,es);break;default:break;}/* quals, sort keys, etc */switch (nodeTag(plan)){case T_IndexScan:show_scan_qual(((IndexScan *) plan)->indexqualorig,"Index Cond", planstate, ancestors, es);if (((IndexScan *) plan)->indexqualorig)show_instrumentation_count("Rows Removed by Index Recheck", 2,planstate, es);show_scan_qual(((IndexScan *) plan)->indexorderbyorig,"Order By", planstate, ancestors, es);show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);break;case T_IndexOnlyScan:show_scan_qual(((IndexOnlyScan *) plan)->indexqual,"Index Cond", planstate, ancestors, es);if (((IndexOnlyScan *) plan)->indexqual)show_instrumentation_count("Rows Removed by Index Recheck", 2,planstate, es);show_scan_qual(((IndexOnlyScan *) plan)->indexorderby,"Order By", planstate, ancestors, es);show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);if (es->analyze)ExplainPropertyFloat("Heap Fetches", NULL,planstate->instrument->ntuples2, 0, es);break;case T_BitmapIndexScan:show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,"Index Cond", planstate, ancestors, es);break;case T_BitmapHeapScan:show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,"Recheck Cond", planstate, ancestors, es);if (((BitmapHeapScan *) plan)->bitmapqualorig)show_instrumentation_count("Rows Removed by Index Recheck", 2,planstate, es);show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);if (es->analyze)show_tidbitmap_info((BitmapHeapScanState *) planstate, es);break;case T_SampleScan:show_tablesample(((SampleScan *) plan)->tablesample,planstate, ancestors, es);/* fall through to print additional fields the same as SeqScan *//* FALLTHROUGH */case T_SeqScan:case T_ValuesScan:case T_CteScan:case T_NamedTuplestoreScan:case T_WorkTableScan:case T_SubqueryScan:show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);break;case T_Gather:{Gather *gather = (Gather *) plan;show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);ExplainPropertyInteger("Workers Planned", NULL,gather->num_workers, es);/* Show params evaluated at gather node */if (gather->initParam)show_eval_params(gather->initParam, es);if (es->analyze){int nworkers;nworkers = ((GatherState *) planstate)->nworkers_launched;ExplainPropertyInteger("Workers Launched", NULL,nworkers, es);}/** Print per-worker Jit instrumentation. Use same conditions* as for the leader's JIT instrumentation, see comment there.*/if (es->costs && es->verbose &&outerPlanState(planstate)->worker_jit_instrument){PlanState *child = outerPlanState(planstate);int n;SharedJitInstrumentation *w = child->worker_jit_instrument;for (n = 0; n < w->num_workers; ++n){ExplainPrintJIT(es, child->state->es_jit_flags,&w->jit_instr[n], n);}}if (gather->single_copy || es->format != EXPLAIN_FORMAT_TEXT)ExplainPropertyBool("Single Copy", gather->single_copy, es);}break;case T_GatherMerge:{GatherMerge *gm = (GatherMerge *) plan;show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);ExplainPropertyInteger("Workers Planned", NULL,gm->num_workers, es);/* Show params evaluated at gather-merge node */if (gm->initParam)show_eval_params(gm->initParam, es);if (es->analyze){int nworkers;nworkers = ((GatherMergeState *) planstate)->nworkers_launched;ExplainPropertyInteger("Workers Launched", NULL,nworkers, es);}}break;case T_FunctionScan:if (es->verbose){List *fexprs = NIL;ListCell *lc;foreach(lc, ((FunctionScan *) plan)->functions){RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);fexprs = lappend(fexprs, rtfunc->funcexpr);}/* We rely on show_expression to insert commas as needed */show_expression((Node *) fexprs,"Function Call", planstate, ancestors,es->verbose, es);}show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);break;case T_TableFuncScan:if (es->verbose){TableFunc *tablefunc = ((TableFuncScan *) plan)->tablefunc;show_expression((Node *) tablefunc,"Table Function Call", planstate, ancestors,es->verbose, es);}show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);break;case T_TidScan:{/** The tidquals list has OR semantics, so be sure to show it* as an OR condition.*/List *tidquals = ((TidScan *) plan)->tidquals;if (list_length(tidquals) > 1)tidquals = list_make1(make_orclause(tidquals));show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);}break;case T_ForeignScan:show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);show_foreignscan_info((ForeignScanState *) planstate, es);break;case T_CustomScan:{CustomScanState *css = (CustomScanState *) planstate;show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);if (css->methods->ExplainCustomScan)css->methods->ExplainCustomScan(css, ancestors, es);}break;case T_NestLoop:show_upper_qual(((NestLoop *) plan)->join.joinqual,"Join Filter", planstate, ancestors, es);if (((NestLoop *) plan)->join.joinqual)show_instrumentation_count("Rows Removed by Join Filter", 1,planstate, es);show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 2,planstate, es);break;case T_MergeJoin:show_upper_qual(((MergeJoin *) plan)->mergeclauses,"Merge Cond", planstate, ancestors, es);show_upper_qual(((MergeJoin *) plan)->join.joinqual,"Join Filter", planstate, ancestors, es);if (((MergeJoin *) plan)->join.joinqual)show_instrumentation_count("Rows Removed by Join Filter", 1,planstate, es);show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 2,planstate, es);break;case T_HashJoin:show_upper_qual(((HashJoin *) plan)->hashclauses,"Hash Cond", planstate, ancestors, es);show_upper_qual(((HashJoin *) plan)->join.joinqual,"Join Filter", planstate, ancestors, es);if (((HashJoin *) plan)->join.joinqual)show_instrumentation_count("Rows Removed by Join Filter", 1,planstate, es);show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 2,planstate, es);break;case T_Agg:show_agg_keys(castNode(AggState, planstate), ancestors, es);show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);break;case T_Group:show_group_keys(castNode(GroupState, planstate), ancestors, es);show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);break;case T_Sort:show_sort_keys(castNode(SortState, planstate), ancestors, es);show_sort_info(castNode(SortState, planstate), es);break;case T_MergeAppend:show_merge_append_keys(castNode(MergeAppendState, planstate),ancestors, es);break;case T_Result:show_upper_qual((List *) ((Result *) plan)->resconstantqual,"One-Time Filter", planstate, ancestors, es);show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);if (plan->qual)show_instrumentation_count("Rows Removed by Filter", 1,planstate, es);break;case T_ModifyTable:show_modifytable_info(castNode(ModifyTableState, planstate), ancestors,es);break;case T_Hash:show_hash_info(castNode(HashState, planstate), es);break;default:break;}/* Show buffer usage */if (es->buffers && planstate->instrument)show_buffer_usage(es, &planstate->instrument->bufusage);/* Show worker detail */if (es->analyze && es->verbose && planstate->worker_instrument){WorkerInstrumentation *w = planstate->worker_instrument;bool opened_group = false;int n;for (n = 0; n < w->num_workers; ++n){Instrumentation *instrument = &w->instrument[n];double nloops = instrument->nloops;double startup_ms;double total_ms;double rows;if (nloops <= 0)continue;startup_ms = 1000.0 * instrument->startup nloops;total_ms = 1000.0 * instrument->total nloops;rows = instrument->ntuples nloops;if (es->format == EXPLAIN_FORMAT_TEXT){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfo(es->str, "Worker %d: ", n);if (es->timing)appendStringInfo(es->str,"actual time=%.3f..%.3f rows=%.0f loops=%.0f\n",startup_ms, total_ms, rows, nloops);elseappendStringInfo(es->str,"actual rows=%.0f loops=%.0f\n",rows, nloops);es->indent++;if (es->buffers)show_buffer_usage(es, &instrument->bufusage);es->indent--;}else{if (!opened_group){ExplainOpenGroup("Workers", "Workers", false, es);opened_group = true;}ExplainOpenGroup("Worker", NULL, true, es);ExplainPropertyInteger("Worker Number", NULL, n, es);if (es->timing){ExplainPropertyFloat("Actual Startup Time", "ms",startup_ms, 3, es);ExplainPropertyFloat("Actual Total Time", "ms",total_ms, 3, es);}ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es);ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es);if (es->buffers)show_buffer_usage(es, &instrument->bufusage);ExplainCloseGroup("Worker", NULL, true, es);}}if (opened_group)ExplainCloseGroup("Workers", "Workers", false, es);}/** If partition pruning was done during executor initialization, the* number of child plans we'll display below will be less than the number* of subplans that was specified in the plan. To make this a bit less* mysterious, emit an indication that this happened. Note that this* field is emitted now because we want it to be a property of the parent* node; it *cannot* be emitted within the Plans sub-node we'll open next.*/switch (nodeTag(plan)){case T_Append:ExplainMissingMembers(((AppendState *) planstate)->as_nplans,list_length(((Append *) plan)->appendplans),es);break;case T_MergeAppend:ExplainMissingMembers(((MergeAppendState *) planstate)->ms_nplans,list_length(((MergeAppend *) plan)->mergeplans),es);break;default:break;}/* Get ready to display the child plans */haschildren = planstate->initPlan ||outerPlanState(planstate) ||innerPlanState(planstate) ||IsA(plan, ModifyTable) ||IsA(plan, Append) ||IsA(plan, MergeAppend) ||IsA(plan, BitmapAnd) ||IsA(plan, BitmapOr) ||IsA(plan, SubqueryScan) ||(IsA(planstate, CustomScanState) &&((CustomScanState *) planstate)->custom_ps != NIL) ||planstate->subPlan;if (haschildren){ExplainOpenGroup("Plans", "Plans", false, es);/* Pass current PlanState as head of ancestors list for children */ancestors = lcons(planstate, ancestors);}/* initPlan-s */if (planstate->initPlan)ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es);/* lefttree */if (outerPlanState(planstate))ExplainNode(outerPlanState(planstate), ancestors,"Outer", NULL, es);/* righttree */if (innerPlanState(planstate))ExplainNode(innerPlanState(planstate), ancestors,"Inner", NULL, es);/* special child plans */switch (nodeTag(plan)){case T_ModifyTable:ExplainMemberNodes(((ModifyTableState *) planstate)->mt_plans,((ModifyTableState *) planstate)->mt_nplans,ancestors, es);break;case T_Append:ExplainMemberNodes(((AppendState *) planstate)->appendplans,((AppendState *) planstate)->as_nplans,ancestors, es);break;case T_MergeAppend:ExplainMemberNodes(((MergeAppendState *) planstate)->mergeplans,((MergeAppendState *) planstate)->ms_nplans,ancestors, es);break;case T_BitmapAnd:ExplainMemberNodes(((BitmapAndState *) planstate)->bitmapplans,((BitmapAndState *) planstate)->nplans,ancestors, es);break;case T_BitmapOr:ExplainMemberNodes(((BitmapOrState *) planstate)->bitmapplans,((BitmapOrState *) planstate)->nplans,ancestors, es);break;case T_SubqueryScan:ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,"Subquery", NULL, es);break;case T_CustomScan:ExplainCustomChildren((CustomScanState *) planstate,ancestors, es);break;default:break;}/* subPlan-s */if (planstate->subPlan)ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es);/* end of child plans */if (haschildren){ancestors = list_delete_first(ancestors);ExplainCloseGroup("Plans", "Plans", false, es);}/* in text format, undo whatever indentation we added */if (es->format == EXPLAIN_FORMAT_TEXT)es->indent = save_indent;ExplainCloseGroup("Plan",relationship ? NULL : "Plan",true, es);}/** Show the targetlist of a plan node*/static voidshow_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es){Plan *plan = planstate->plan;List *context;List *result = NIL;bool useprefix;ListCell *lc;/* No work if empty tlist (this occurs eg in bitmap indexscans) */if (plan->targetlist == NIL)return;/* The tlist of an Append isn't real helpful, so suppress it */if (IsA(plan, Append))return;/* Likewise for MergeAppend and RecursiveUnion */if (IsA(plan, MergeAppend))return;if (IsA(plan, RecursiveUnion))return;/** Likewise for ForeignScan that executes a direct INSERT/UPDATE/DELETE** Note: the tlist for a ForeignScan that executes a direct INSERT/UPDATE* might contain subplan output expressions that are confusing in this* context. The tlist for a ForeignScan that executes a direct UPDATE/* DELETE always contains "junk" target columns to identify the exact row* to update or delete, which would be confusing in this context. So, we* suppress it in all the cases.*/if (IsA(plan, ForeignScan) &&((ForeignScan *) plan)->operation != CMD_SELECT)return;/* Set up deparsing context */context = set_deparse_context_planstate(es->deparse_cxt,(Node *) planstate,ancestors);useprefix = list_length(es->rtable) > 1;/* Deparse each result column (we now include resjunk ones) */foreach(lc, plan->targetlist){TargetEntry *tle = (TargetEntry *) lfirst(lc);result = lappend(result,deparse_expression((Node *) tle->expr, context,useprefix, false));}/* Print results */ExplainPropertyList("Output", result, es);}/** Show a generic expression*/static voidshow_expression(Node *node, const char *qlabel,PlanState *planstate, List *ancestors,bool useprefix, ExplainState *es){List *context;char *exprstr;/* Set up deparsing context */context = set_deparse_context_planstate(es->deparse_cxt,(Node *) planstate,ancestors);/* Deparse the expression */exprstr = deparse_expression(node, context, useprefix, false);/* And add to es->str */ExplainPropertyText(qlabel, exprstr, es);}/** Show a qualifier expression (which is a List with implicit AND semantics)*/static voidshow_qual(List *qual, const char *qlabel,PlanState *planstate, List *ancestors,bool useprefix, ExplainState *es){Node *node;/* No work if empty qual */if (qual == NIL)return;/* Convert AND list to explicit AND */node = (Node *) make_ands_explicit(qual);/* And show it */show_expression(node, qlabel, planstate, ancestors, useprefix, es);}/** Show a qualifier expression for a scan plan node*/static voidshow_scan_qual(List *qual, const char *qlabel,PlanState *planstate, List *ancestors,ExplainState *es){bool useprefix;useprefix = (IsA(planstate->plan, SubqueryScan) ||es->verbose);show_qual(qual, qlabel, planstate, ancestors, useprefix, es);}/** Show a qualifier expression for an upper-level plan node*/static voidshow_upper_qual(List *qual, const char *qlabel,PlanState *planstate, List *ancestors,ExplainState *es){bool useprefix;useprefix = (list_length(es->rtable) > 1 || es->verbose);show_qual(qual, qlabel, planstate, ancestors, useprefix, es);}/** Show the sort keys for a Sort node.*/static voidshow_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es){Sort *plan = (Sort *) sortstate->ss.ps.plan;show_sort_group_keys((PlanState *) sortstate, "Sort Key",plan->numCols, plan->sortColIdx,plan->sortOperators, plan->collations,plan->nullsFirst,ancestors, es);}/** Likewise, for a MergeAppend node.*/static voidshow_merge_append_keys(MergeAppendState *mstate, List *ancestors,ExplainState *es){MergeAppend *plan = (MergeAppend *) mstate->ps.plan;show_sort_group_keys((PlanState *) mstate, "Sort Key",plan->numCols, plan->sortColIdx,plan->sortOperators, plan->collations,plan->nullsFirst,ancestors, es);}/** Show the grouping keys for an Agg node.*/static voidshow_agg_keys(AggState *astate, List *ancestors,ExplainState *es){Agg *plan = (Agg *) astate->ss.ps.plan;if (plan->numCols > 0 || plan->groupingSets){/* The key columns refer to the tlist of the child plan */ancestors = lcons(astate, ancestors);if (plan->groupingSets)show_grouping_sets(outerPlanState(astate), plan, ancestors, es);elseshow_sort_group_keys(outerPlanState(astate), "Group Key",plan->numCols, plan->grpColIdx,NULL, NULL, NULL,ancestors, es);ancestors = list_delete_first(ancestors);}}static voidshow_grouping_sets(PlanState *planstate, Agg *agg,List *ancestors, ExplainState *es){List *context;bool useprefix;ListCell *lc;/* Set up deparsing context */context = set_deparse_context_planstate(es->deparse_cxt,(Node *) planstate,ancestors);useprefix = (list_length(es->rtable) > 1 || es->verbose);ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es);show_grouping_set_keys(planstate, agg, NULL,context, useprefix, ancestors, es);foreach(lc, agg->chain){Agg *aggnode = lfirst(lc);Sort *sortnode = (Sort *) aggnode->plan.lefttree;show_grouping_set_keys(planstate, aggnode, sortnode,context, useprefix, ancestors, es);}ExplainCloseGroup("Grouping Sets", "Grouping Sets", false, es);}static voidshow_grouping_set_keys(PlanState *planstate,Agg *aggnode, Sort *sortnode,List *context, bool useprefix,List *ancestors, ExplainState *es){Plan *plan = planstate->plan;char *exprstr;ListCell *lc;List *gsets = aggnode->groupingSets;AttrNumber *keycols = aggnode->grpColIdx;const char *keyname;const char *keysetname;if (aggnode->aggstrategy == AGG_HASHED || aggnode->aggstrategy == AGG_MIXED){keyname = "Hash Key";keysetname = "Hash Keys";}else{keyname = "Group Key";keysetname = "Group Keys";}ExplainOpenGroup("Grouping Set", NULL, true, es);if (sortnode){show_sort_group_keys(planstate, "Sort Key",sortnode->numCols, sortnode->sortColIdx,sortnode->sortOperators, sortnode->collations,sortnode->nullsFirst,ancestors, es);if (es->format == EXPLAIN_FORMAT_TEXT)es->indent++;}ExplainOpenGroup(keysetname, keysetname, false, es);foreach(lc, gsets){List *result = NIL;ListCell *lc2;foreach(lc2, (List *) lfirst(lc)){Index i = lfirst_int(lc2);AttrNumber keyresno = keycols[i];TargetEntry *target = get_tle_by_resno(plan->targetlist,keyresno);if (!target)elog(ERROR, "no tlist entry for key %d", keyresno);/* Deparse the expression, showing any top-level cast */exprstr = deparse_expression((Node *) target->expr, context,useprefix, true);result = lappend(result, exprstr);}if (!result && es->format == EXPLAIN_FORMAT_TEXT)ExplainPropertyText(keyname, "()", es);elseExplainPropertyListNested(keyname, result, es);}ExplainCloseGroup(keysetname, keysetname, false, es);if (sortnode && es->format == EXPLAIN_FORMAT_TEXT)es->indent--;ExplainCloseGroup("Grouping Set", NULL, true, es);}/** Show the grouping keys for a Group node.*/static voidshow_group_keys(GroupState *gstate, List *ancestors,ExplainState *es){Group *plan = (Group *) gstate->ss.ps.plan;/* The key columns refer to the tlist of the child plan */ancestors = lcons(gstate, ancestors);show_sort_group_keys(outerPlanState(gstate), "Group Key",plan->numCols, plan->grpColIdx,NULL, NULL, NULL,ancestors, es);ancestors = list_delete_first(ancestors);}/** Common code to show sort/group keys, which are represented in plan nodes* as arrays of targetlist indexes. If it's a sort key rather than a group* key, also pass sort operators/collations/nullsFirst arrays.*/static voidshow_sort_group_keys(PlanState *planstate, const char *qlabel,int nkeys, AttrNumber *keycols,Oid *sortOperators, Oid *collations, bool *nullsFirst,List *ancestors, ExplainState *es){Plan *plan = planstate->plan;List *context;List *result = NIL;StringInfoData sortkeybuf;bool useprefix;int keyno;if (nkeys <= 0)return;initStringInfo(&sortkeybuf);/* Set up deparsing context */context = set_deparse_context_planstate(es->deparse_cxt,(Node *) planstate,ancestors);useprefix = (list_length(es->rtable) > 1 || es->verbose);for (keyno = 0; keyno < nkeys; keyno++){/* find key expression in tlist */AttrNumber keyresno = keycols[keyno];TargetEntry *target = get_tle_by_resno(plan->targetlist,keyresno);char *exprstr;if (!target)elog(ERROR, "no tlist entry for key %d", keyresno);/* Deparse the expression, showing any top-level cast */exprstr = deparse_expression((Node *) target->expr, context,useprefix, true);resetStringInfo(&sortkeybuf);appendStringInfoString(&sortkeybuf, exprstr);/* Append sort order information, if relevant */if (sortOperators != NULL)show_sortorder_options(&sortkeybuf,(Node *) target->expr,sortOperators[keyno],collations[keyno],nullsFirst[keyno]);/* Emit one property-list item per sort key */result = lappend(result, pstrdup(sortkeybuf.data));}ExplainPropertyList(qlabel, result, es);}/** Append nondefault characteristics of the sort ordering of a column to buf* (collation, direction, NULLS FIRST/LAST)*/static voidshow_sortorder_options(StringInfo buf, Node *sortexpr,Oid sortOperator, Oid collation, bool nullsFirst){Oid sortcoltype = exprType(sortexpr);bool reverse = false;TypeCacheEntry *typentry;typentry = lookup_type_cache(sortcoltype,TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);/** Print COLLATE if it's not default for the column's type. There are* some cases where this is redundant, eg if expression is a column whose* declared collation is that collation, but it's hard to distinguish that* here (and arguably, printing COLLATE explicitly is a good idea anyway* in such cases).*/if (OidIsValid(collation) && collation != get_typcollation(sortcoltype)){char *collname = get_collation_name(collation);if (collname == NULL)elog(ERROR, "cache lookup failed for collation %u", collation);appendStringInfo(buf, " COLLATE %s", quote_identifier(collname));}/* Print direction if not ASC, or USING if non-default sort operator */if (sortOperator == typentry->gt_opr){appendStringInfoString(buf, " DESC");reverse = true;}else if (sortOperator != typentry->lt_opr){char *opname = get_opname(sortOperator);if (opname == NULL)elog(ERROR, "cache lookup failed for operator %u", sortOperator);appendStringInfo(buf, " USING %s", opname);/* Determine whether operator would be considered ASC or DESC */(void) get_equality_op_for_ordering_op(sortOperator, &reverse);}/* Add NULLS FIRST/LAST only if it wouldn't be default */if (nullsFirst && !reverse){appendStringInfoString(buf, " NULLS FIRST");}else if (!nullsFirst && reverse){appendStringInfoString(buf, " NULLS LAST");}}/** Show TABLESAMPLE properties*/static voidshow_tablesample(TableSampleClause *tsc, PlanState *planstate,List *ancestors, ExplainState *es){List *context;bool useprefix;char *method_name;List *params = NIL;char *repeatable;ListCell *lc;/* Set up deparsing context */context = set_deparse_context_planstate(es->deparse_cxt,(Node *) planstate,ancestors);useprefix = list_length(es->rtable) > 1;/* Get the tablesample method name */method_name = get_func_name(tsc->tsmhandler);/* Deparse parameter expressions */foreach(lc, tsc->args){Node *arg = (Node *) lfirst(lc);params = lappend(params,deparse_expression(arg, context,useprefix, false));}if (tsc->repeatable)repeatable = deparse_expression((Node *) tsc->repeatable, context,useprefix, false);elserepeatable = NULL;/* Print results */if (es->format == EXPLAIN_FORMAT_TEXT){bool first = true;appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfo(es->str, "Sampling: %s (", method_name);foreach(lc, params){if (!first)appendStringInfoString(es->str, ", ");appendStringInfoString(es->str, (const char *) lfirst(lc));first = false;}appendStringInfoChar(es->str, ')');if (repeatable)appendStringInfo(es->str, " REPEATABLE (%s)", repeatable);appendStringInfoChar(es->str, '\n');}else{ExplainPropertyText("Sampling Method", method_name, es);ExplainPropertyList("Sampling Parameters", params, es);if (repeatable)ExplainPropertyText("Repeatable Seed", repeatable, es);}}/** If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node*/static voidshow_sort_info(SortState *sortstate, ExplainState *es){if (!es->analyze)return;if (sortstate->sort_Done && sortstate->tuplesortstate != NULL){Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;TuplesortInstrumentation stats;const char *sortMethod;const char *spaceType;long spaceUsed;tuplesort_get_stats(state, &stats);sortMethod = tuplesort_method_name(stats.sortMethod);spaceType = tuplesort_space_type_name(stats.spaceType);spaceUsed = stats.spaceUsed;if (es->format == EXPLAIN_FORMAT_TEXT){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfo(es->str, "Sort Method: %s %s: %ldkB\n",sortMethod, spaceType, spaceUsed);}else{ExplainPropertyText("Sort Method", sortMethod, es);ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es);ExplainPropertyText("Sort Space Type", spaceType, es);}}if (sortstate->shared_info != NULL){int n;bool opened_group = false;for (n = 0; n < sortstate->shared_info->num_workers; n++){TuplesortInstrumentation *sinstrument;const char *sortMethod;const char *spaceType;long spaceUsed;sinstrument = &sortstate->shared_info->sinstrument[n];if (sinstrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS)continue; /* ignore any unfilled slots */sortMethod = tuplesort_method_name(sinstrument->sortMethod);spaceType = tuplesort_space_type_name(sinstrument->spaceType);spaceUsed = sinstrument->spaceUsed;if (es->format == EXPLAIN_FORMAT_TEXT){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfo(es->str,"Worker %d: Sort Method: %s %s: %ldkB\n",n, sortMethod, spaceType, spaceUsed);}else{if (!opened_group){ExplainOpenGroup("Workers", "Workers", false, es);opened_group = true;}ExplainOpenGroup("Worker", NULL, true, es);ExplainPropertyInteger("Worker Number", NULL, n, es);ExplainPropertyText("Sort Method", sortMethod, es);ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es);ExplainPropertyText("Sort Space Type", spaceType, es);ExplainCloseGroup("Worker", NULL, true, es);}}if (opened_group)ExplainCloseGroup("Workers", "Workers", false, es);}}/** Show information on hash buckets/batches.*/static voidshow_hash_info(HashState *hashstate, ExplainState *es){HashInstrumentation hinstrument = {0};/** In a parallel query, the leader process may or may not have run the* hash join, and even if it did it may not have built a hash table due to* timing (if it started late it might have seen no tuples in the outer* relation and skipped building the hash table). Therefore we have to be* prepared to get instrumentation data from all participants.*/if (hashstate->hashtable)ExecHashGetInstrumentation(&hinstrument, hashstate->hashtable);/** Merge results from workers. In the parallel-oblivious case, the* results from all participants should be identical, except where* participants didn't run the join at all so have no data. In the* parallel-aware case, we need to consider all the results. Each worker* may have seen a different subset of batches and we want to find the* highest memory usage for any one batch across all batches.*/if (hashstate->shared_info){SharedHashInfo *shared_info = hashstate->shared_info;int i;for (i = 0; i < shared_info->num_workers; ++i){HashInstrumentation *worker_hi = &shared_info->hinstrument[i];if (worker_hi->nbatch > 0){/** Every participant should agree on the buckets, so to be* sure we have a value we'll just overwrite each time.*/hinstrument.nbuckets = worker_hi->nbuckets;hinstrument.nbuckets_original = worker_hi->nbuckets_original;/** Normally every participant should agree on the number of* batches too, but it's possible for a backend that started* late and missed the whole join not to have the final nbatch* number. So we'll take the largest number.*/hinstrument.nbatch = Max(hinstrument.nbatch, worker_hi->nbatch);hinstrument.nbatch_original = worker_hi->nbatch_original;/** In a parallel-aware hash join, for now we report the* maximum peak memory reported by any worker.*/hinstrument.space_peak =Max(hinstrument.space_peak, worker_hi->space_peak);}}}if (hinstrument.nbatch > 0){long spacePeakKb = (hinstrument.space_peak + 1023) 1024;if (es->format != EXPLAIN_FORMAT_TEXT){ExplainPropertyInteger("Hash Buckets", NULL,hinstrument.nbuckets, es);ExplainPropertyInteger("Original Hash Buckets", NULL,hinstrument.nbuckets_original, es);ExplainPropertyInteger("Hash Batches", NULL,hinstrument.nbatch, es);ExplainPropertyInteger("Original Hash Batches", NULL,hinstrument.nbatch_original, es);ExplainPropertyInteger("Peak Memory Usage", "kB",spacePeakKb, es);}else if (hinstrument.nbatch_original != hinstrument.nbatch ||hinstrument.nbuckets_original != hinstrument.nbuckets){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfo(es->str,"Buckets: %d (originally %d) Batches: %d (originally %d) Memory Usage: %ldkB\n",hinstrument.nbuckets,hinstrument.nbuckets_original,hinstrument.nbatch,hinstrument.nbatch_original,spacePeakKb);}else{appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfo(es->str,"Buckets: %d Batches: %d Memory Usage: %ldkB\n",hinstrument.nbuckets, hinstrument.nbatch,spacePeakKb);}}}/** If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node*/static voidshow_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es){if (es->format != EXPLAIN_FORMAT_TEXT){ExplainPropertyInteger("Exact Heap Blocks", NULL,planstate->exact_pages, es);ExplainPropertyInteger("Lossy Heap Blocks", NULL,planstate->lossy_pages, es);}else{if (planstate->exact_pages > 0 || planstate->lossy_pages > 0){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfoString(es->str, "Heap Blocks:");if (planstate->exact_pages > 0)appendStringInfo(es->str, " exact=%ld", planstate->exact_pages);if (planstate->lossy_pages > 0)appendStringInfo(es->str, " lossy=%ld", planstate->lossy_pages);appendStringInfoChar(es->str, '\n');}}}/** If it's EXPLAIN ANALYZE, show instrumentation information for a plan node** "which" identifies which instrumentation counter to print*/static voidshow_instrumentation_count(const char *qlabel, int which,PlanState *planstate, ExplainState *es){double nfiltered;double nloops;if (!es->analyze || !planstate->instrument)return;if (which == 2)nfiltered = planstate->instrument->nfiltered2;elsenfiltered = planstate->instrument->nfiltered1;nloops = planstate->instrument->nloops;/* In text mode, suppress zero counts; they're not interesting enough */if (nfiltered > 0 || es->format != EXPLAIN_FORMAT_TEXT){if (nloops > 0)ExplainPropertyFloat(qlabel, NULL, nfiltered nloops, 0, es);elseExplainPropertyFloat(qlabel, NULL, 0.0, 0, es);}}/** Show extra information for a ForeignScan node.*/static voidshow_foreignscan_info(ForeignScanState *fsstate, ExplainState *es){FdwRoutine *fdwroutine = fsstate->fdwroutine;/* Let the FDW emit whatever fields it wants */if (((ForeignScan *) fsstate->ss.ps.plan)->operation != CMD_SELECT){if (fdwroutine->ExplainDirectModify != NULL)fdwroutine->ExplainDirectModify(fsstate, es);}else{if (fdwroutine->ExplainForeignScan != NULL)fdwroutine->ExplainForeignScan(fsstate, es);}}/** Show initplan params evaluated at Gather or Gather Merge node.*/static voidshow_eval_params(Bitmapset *bms_params, ExplainState *es){int paramid = -1;List *params = NIL;Assert(bms_params);while ((paramid = bms_next_member(bms_params, paramid)) >= 0){char param[32];snprintf(param, sizeof(param), "$%d", paramid);params = lappend(params, pstrdup(param));}if (params)ExplainPropertyList("Params Evaluated", params, es);}/** Fetch the name of an index in an EXPLAIN** We allow plugins to get control here so that plans involving hypothetical* indexes can be explained.** Note: names returned by this function should be "raw"; the caller will* apply quoting if needed. Formerly the convention was to do quoting here,* but we don't want that in non-text output formats.*/static const char *explain_get_index_name(Oid indexId){const char *result;if (explain_get_index_name_hook)result = (*explain_get_index_name_hook) (indexId);elseresult = NULL;if (result == NULL){/* default behavior: look it up in the catalogs */result = get_rel_name(indexId);if (result == NULL)elog(ERROR, "cache lookup failed for index %u", indexId);}return result;}/** Show buffer usage details.*/static voidshow_buffer_usage(ExplainState *es, const BufferUsage *usage){if (es->format == EXPLAIN_FORMAT_TEXT){bool has_shared = (usage->shared_blks_hit > 0 ||usage->shared_blks_read > 0 ||usage->shared_blks_dirtied > 0 ||usage->shared_blks_written > 0);bool has_local = (usage->local_blks_hit > 0 ||usage->local_blks_read > 0 ||usage->local_blks_dirtied > 0 ||usage->local_blks_written > 0);bool has_temp = (usage->temp_blks_read > 0 ||usage->temp_blks_written > 0);bool has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) ||!INSTR_TIME_IS_ZERO(usage->blk_write_time));/* Show only positive counter values. */if (has_shared || has_local || has_temp){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfoString(es->str, "Buffers:");if (has_shared){appendStringInfoString(es->str, " shared");if (usage->shared_blks_hit > 0)appendStringInfo(es->str, " hit=%ld",usage->shared_blks_hit);if (usage->shared_blks_read > 0)appendStringInfo(es->str, " read=%ld",usage->shared_blks_read);if (usage->shared_blks_dirtied > 0)appendStringInfo(es->str, " dirtied=%ld",usage->shared_blks_dirtied);if (usage->shared_blks_written > 0)appendStringInfo(es->str, " written=%ld",usage->shared_blks_written);if (has_local || has_temp)appendStringInfoChar(es->str, ',');}if (has_local){appendStringInfoString(es->str, " local");if (usage->local_blks_hit > 0)appendStringInfo(es->str, " hit=%ld",usage->local_blks_hit);if (usage->local_blks_read > 0)appendStringInfo(es->str, " read=%ld",usage->local_blks_read);if (usage->local_blks_dirtied > 0)appendStringInfo(es->str, " dirtied=%ld",usage->local_blks_dirtied);if (usage->local_blks_written > 0)appendStringInfo(es->str, " written=%ld",usage->local_blks_written);if (has_temp)appendStringInfoChar(es->str, ',');}if (has_temp){appendStringInfoString(es->str, " temp");if (usage->temp_blks_read > 0)appendStringInfo(es->str, " read=%ld",usage->temp_blks_read);if (usage->temp_blks_written > 0)appendStringInfo(es->str, " written=%ld",usage->temp_blks_written);}appendStringInfoChar(es->str, '\n');}/* As above, show only positive counter values. */if (has_timing){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfoString(es->str, "I/O Timings:");if (!INSTR_TIME_IS_ZERO(usage->blk_read_time))appendStringInfo(es->str, " read=%0.3f",INSTR_TIME_GET_MILLISEC(usage->blk_read_time));if (!INSTR_TIME_IS_ZERO(usage->blk_write_time))appendStringInfo(es->str, " write=%0.3f",INSTR_TIME_GET_MILLISEC(usage->blk_write_time));appendStringInfoChar(es->str, '\n');}}else{ExplainPropertyInteger("Shared Hit Blocks", NULL,usage->shared_blks_hit, es);ExplainPropertyInteger("Shared Read Blocks", NULL,usage->shared_blks_read, es);ExplainPropertyInteger("Shared Dirtied Blocks", NULL,usage->shared_blks_dirtied, es);ExplainPropertyInteger("Shared Written Blocks", NULL,usage->shared_blks_written, es);ExplainPropertyInteger("Local Hit Blocks", NULL,usage->local_blks_hit, es);ExplainPropertyInteger("Local Read Blocks", NULL,usage->local_blks_read, es);ExplainPropertyInteger("Local Dirtied Blocks", NULL,usage->local_blks_dirtied, es);ExplainPropertyInteger("Local Written Blocks", NULL,usage->local_blks_written, es);ExplainPropertyInteger("Temp Read Blocks", NULL,usage->temp_blks_read, es);ExplainPropertyInteger("Temp Written Blocks", NULL,usage->temp_blks_written, es);if (track_io_timing){ExplainPropertyFloat("I/O Read Time", "ms",INSTR_TIME_GET_MILLISEC(usage->blk_read_time),3, es);ExplainPropertyFloat("I/O Write Time", "ms",INSTR_TIME_GET_MILLISEC(usage->blk_write_time),3, es);}}}/** Add some additional details about an IndexScan or IndexOnlyScan*/static voidExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,ExplainState *es){const char *indexname = explain_get_index_name(indexid);if (es->format == EXPLAIN_FORMAT_TEXT){if (ScanDirectionIsBackward(indexorderdir))appendStringInfoString(es->str, " Backward");appendStringInfo(es->str, " using %s", quote_identifier(indexname));}else{const char *scandir;switch (indexorderdir){case BackwardScanDirection:scandir = "Backward";break;case NoMovementScanDirection:scandir = "NoMovement";break;case ForwardScanDirection:scandir = "Forward";break;default:scandir = "???";break;}ExplainPropertyText("Scan Direction", scandir, es);ExplainPropertyText("Index Name", indexname, es);}}/** Show the target of a Scan node*/static voidExplainScanTarget(Scan *plan, ExplainState *es){ExplainTargetRel((Plan *) plan, plan->scanrelid, es);}/** Show the target of a ModifyTable node** Here we show the nominal target (ie, the relation that was named in the* original query). If the actual target(s) is/are different, we'll show them* in show_modifytable_info().*/static voidExplainModifyTarget(ModifyTable *plan, ExplainState *es){ExplainTargetRel((Plan *) plan, plan->nominalRelation, es);}/** Show the target relation of a scan or modify node*/static voidExplainTargetRel(Plan *plan, Index rti, ExplainState *es){char *objectname = NULL;char *namespace = NULL;const char *objecttag = NULL;RangeTblEntry *rte;char *refname;rte = rt_fetch(rti, es->rtable);refname = (char *) list_nth(es->rtable_names, rti - 1);if (refname == NULL)refname = rte->eref->aliasname;switch (nodeTag(plan)){case T_SeqScan:case T_SampleScan:case T_IndexScan:case T_IndexOnlyScan:case T_BitmapHeapScan:case T_TidScan:case T_ForeignScan:case T_CustomScan:case T_ModifyTable:/* Assert it's on a real relation */Assert(rte->rtekind == RTE_RELATION);objectname = get_rel_name(rte->relid);if (es->verbose)namespace = get_namespace_name(get_rel_namespace(rte->relid));objecttag = "Relation Name";break;case T_FunctionScan:{FunctionScan *fscan = (FunctionScan *) plan;/* Assert it's on a RangeFunction */Assert(rte->rtekind == RTE_FUNCTION);/** If the expression is still a function call of a single* function, we can get the real name of the function.* Otherwise, punt. (Even if it was a single function call* originally, the optimizer could have simplified it away.)*/if (list_length(fscan->functions) == 1){RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions);if (IsA(rtfunc->funcexpr, FuncExpr)){FuncExpr *funcexpr = (FuncExpr *) rtfunc->funcexpr;Oid funcid = funcexpr->funcid;objectname = get_func_name(funcid);if (es->verbose)namespace =get_namespace_name(get_func_namespace(funcid));}}objecttag = "Function Name";}break;case T_TableFuncScan:Assert(rte->rtekind == RTE_TABLEFUNC);objectname = "xmltable";objecttag = "Table Function Name";break;case T_ValuesScan:Assert(rte->rtekind == RTE_VALUES);break;case T_CteScan:/* Assert it's on a non-self-reference CTE */Assert(rte->rtekind == RTE_CTE);Assert(!rte->self_reference);objectname = rte->ctename;objecttag = "CTE Name";break;case T_NamedTuplestoreScan:Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);objectname = rte->enrname;objecttag = "Tuplestore Name";break;case T_WorkTableScan:/* Assert it's on a self-reference CTE */Assert(rte->rtekind == RTE_CTE);Assert(rte->self_reference);objectname = rte->ctename;objecttag = "CTE Name";break;default:break;}if (es->format == EXPLAIN_FORMAT_TEXT){appendStringInfoString(es->str, " on");if (namespace != NULL)appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),quote_identifier(objectname));else if (objectname != NULL)appendStringInfo(es->str, " %s", quote_identifier(objectname));if (objectname == NULL || strcmp(refname, objectname) != 0)appendStringInfo(es->str, " %s", quote_identifier(refname));}else{if (objecttag != NULL && objectname != NULL)ExplainPropertyText(objecttag, objectname, es);if (namespace != NULL)ExplainPropertyText("Schema", namespace, es);ExplainPropertyText("Alias", refname, es);}}/** Show extra information for a ModifyTable node** We have three objectives here. First, if there's more than one target* table or it's different from the nominal target, identify the actual* target(s). Second, give FDWs a chance to display extra info about foreign* targets. Third, show information about ON CONFLICT.*/static voidshow_modifytable_info(ModifyTableState *mtstate, List *ancestors,ExplainState *es){ModifyTable *node = (ModifyTable *) mtstate->ps.plan;const char *operation;const char *foperation;bool labeltargets;int j;List *idxNames = NIL;ListCell *lst;switch (node->operation){case CMD_INSERT:operation = "Insert";foperation = "Foreign Insert";break;case CMD_UPDATE:operation = "Update";foperation = "Foreign Update";break;case CMD_DELETE:operation = "Delete";foperation = "Foreign Delete";break;default:operation = "???";foperation = "Foreign ???";break;}/* Should we explicitly label target relations? */labeltargets = (mtstate->mt_nplans > 1 ||(mtstate->mt_nplans == 1 &&mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));if (labeltargets)ExplainOpenGroup("Target Tables", "Target Tables", false, es);for (j = 0; j < mtstate->mt_nplans; j++){ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;if (labeltargets){/* Open a group for this target */ExplainOpenGroup("Target Table", NULL, true, es);/** In text mode, decorate each target with operation type, so that* ExplainTargetRel's output of " on foo" will read nicely.*/if (es->format == EXPLAIN_FORMAT_TEXT){appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfoString(es->str,fdwroutine ? foperation : operation);}/* Identify target */ExplainTargetRel((Plan *) node,resultRelInfo->ri_RangeTableIndex,es);if (es->format == EXPLAIN_FORMAT_TEXT){appendStringInfoChar(es->str, '\n');es->indent++;}}/* Give FDW a chance if needed */if (!resultRelInfo->ri_usesFdwDirectModify &&fdwroutine != NULL &&fdwroutine->ExplainForeignModify != NULL){List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);fdwroutine->ExplainForeignModify(mtstate,resultRelInfo,fdw_private,j,es);}if (labeltargets){/* Undo the indentation we added in text format */if (es->format == EXPLAIN_FORMAT_TEXT)es->indent--;/* Close the group */ExplainCloseGroup("Target Table", NULL, true, es);}}/* Gather names of ON CONFLICT arbiter indexes */foreach(lst, node->arbiterIndexes){char *indexname = get_rel_name(lfirst_oid(lst));idxNames = lappend(idxNames, indexname);}if (node->onConflictAction != ONCONFLICT_NONE){ExplainPropertyText("Conflict Resolution",node->onConflictAction == ONCONFLICT_NOTHING ?"NOTHING" : "UPDATE",es);/** Don't display arbiter indexes at all when DO NOTHING variant* implicitly ignores all conflicts*/if (idxNames)ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es);/* ON CONFLICT DO UPDATE WHERE qual is specially displayed */if (node->onConflictWhere){show_upper_qual((List *) node->onConflictWhere, "Conflict Filter",&mtstate->ps, ancestors, es);show_instrumentation_count("Rows Removed by Conflict Filter", 1, &mtstate->ps, es);}/* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */if (es->analyze && mtstate->ps.instrument){double total;double insert_path;double other_path;InstrEndLoop(mtstate->mt_plans[0]->instrument);/* count the number of source rows */total = mtstate->mt_plans[0]->instrument->ntuples;other_path = mtstate->ps.instrument->ntuples2;insert_path = total - other_path;ExplainPropertyFloat("Tuples Inserted", NULL,insert_path, 0, es);ExplainPropertyFloat("Conflicting Tuples", NULL,other_path, 0, es);}}if (labeltargets)ExplainCloseGroup("Target Tables", "Target Tables", false, es);}/** Explain the constituent plans of a ModifyTable, Append, MergeAppend,* BitmapAnd, or BitmapOr node.** The ancestors list should already contain the immediate parent of these* plans.*/static voidExplainMemberNodes(PlanState **planstates, int nplans,List *ancestors, ExplainState *es){int j;for (j = 0; j < nplans; j++)ExplainNode(planstates[j], ancestors,"Member", NULL, es);}/** Report about any pruned subnodes of an Append or MergeAppend node.** nplans indicates the number of live subplans.* nchildren indicates the original number of subnodes in the Plan;* some of these may have been pruned by the run-time pruning code.*/static voidExplainMissingMembers(int nplans, int nchildren, ExplainState *es){if (nplans < nchildren || es->format != EXPLAIN_FORMAT_TEXT)ExplainPropertyInteger("Subplans Removed", NULL,nchildren - nplans, es);}/** Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).** The ancestors list should already contain the immediate parent of these* SubPlanStates.*/static voidExplainSubPlans(List *plans, List *ancestors,const char *relationship, ExplainState *es){ListCell *lst;foreach(lst, plans){SubPlanState *sps = (SubPlanState *) lfirst(lst);SubPlan *sp = sps->subplan;/** There can be multiple SubPlan nodes referencing the same physical* subplan (same plan_id, which is its index in PlannedStmt.subplans).* We should print a subplan only once, so track which ones we already* printed. This state must be global across the plan tree, since the* duplicate nodes could be in different plan nodes, eg both a bitmap* indexscan's indexqual and its parent heapscan's recheck qual. (We* do not worry too much about which plan node we show the subplan as* attached to in such cases.)*/if (bms_is_member(sp->plan_id, es->printed_subplans))continue;es->printed_subplans = bms_add_member(es->printed_subplans,sp->plan_id);ExplainNode(sps->planstate, ancestors,relationship, sp->plan_name, es);}}/** Explain a list of children of a CustomScan.*/static voidExplainCustomChildren(CustomScanState *css, List *ancestors, ExplainState *es){ListCell *cell;const char *label =(list_length(css->custom_ps) != 1 ? "children" : "child");foreach(cell, css->custom_ps)ExplainNode((PlanState *) lfirst(cell), ancestors, label, NULL, es);}/** Explain a property, such as sort keys or targets, that takes the form of* a list of unlabeled items. "data" is a list of C strings.*/voidExplainPropertyList(const char *qlabel, List *data, ExplainState *es){ListCell *lc;bool first = true;switch (es->format){case EXPLAIN_FORMAT_TEXT:appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfo(es->str, "%s: ", qlabel);foreach(lc, data){if (!first)appendStringInfoString(es->str, ", ");appendStringInfoString(es->str, (const char *) lfirst(lc));first = false;}appendStringInfoChar(es->str, '\n');break;case EXPLAIN_FORMAT_XML:ExplainXMLTag(qlabel, X_OPENING, es);foreach(lc, data){char *str;appendStringInfoSpaces(es->str, es->indent * 2 + 2);appendStringInfoString(es->str, "<Item>");str = escape_xml((const char *) lfirst(lc));appendStringInfoString(es->str, str);pfree(str);appendStringInfoString(es->str, "</Item>\n");}ExplainXMLTag(qlabel, X_CLOSING, es);break;case EXPLAIN_FORMAT_JSON:ExplainJSONLineEnding(es);appendStringInfoSpaces(es->str, es->indent * 2);escape_json(es->str, qlabel);appendStringInfoString(es->str, ": [");foreach(lc, data){if (!first)appendStringInfoString(es->str, ", ");escape_json(es->str, (const char *) lfirst(lc));first = false;}appendStringInfoChar(es->str, ']');break;case EXPLAIN_FORMAT_YAML:ExplainYAMLLineStarting(es);appendStringInfo(es->str, "%s: ", qlabel);foreach(lc, data){appendStringInfoChar(es->str, '\n');appendStringInfoSpaces(es->str, es->indent * 2 + 2);appendStringInfoString(es->str, "- ");escape_yaml(es->str, (const char *) lfirst(lc));}break;}}/** Explain a property that takes the form of a list of unlabeled items within* another list. "data" is a list of C strings.*/voidExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es){ListCell *lc;bool first = true;switch (es->format){case EXPLAIN_FORMAT_TEXT:case EXPLAIN_FORMAT_XML:ExplainPropertyList(qlabel, data, es);return;case EXPLAIN_FORMAT_JSON:ExplainJSONLineEnding(es);appendStringInfoSpaces(es->str, es->indent * 2);appendStringInfoChar(es->str, '[');foreach(lc, data){if (!first)appendStringInfoString(es->str, ", ");escape_json(es->str, (const char *) lfirst(lc));first = false;}appendStringInfoChar(es->str, ']');break;case EXPLAIN_FORMAT_YAML:ExplainYAMLLineStarting(es);appendStringInfoString(es->str, "- [");foreach(lc, data){if (!first)appendStringInfoString(es->str, ", ");escape_yaml(es->str, (const char *) lfirst(lc));first = false;}appendStringInfoChar(es->str, ']');break;}}/** Explain a simple property.** If "numeric" is true, the value is a number (or other value that* doesn't need quoting in JSON).** If unit is non-NULL the text format will display it after the value.** This usually should not be invoked directly, but via one of the datatype* specific routines ExplainPropertyText, ExplainPropertyInteger, etc.*/static voidExplainProperty(const char *qlabel, const char *unit, const char *value,bool numeric, ExplainState *es){switch (es->format){case EXPLAIN_FORMAT_TEXT:appendStringInfoSpaces(es->str, es->indent * 2);if (unit)appendStringInfo(es->str, "%s: %s %s\n", qlabel, value, unit);elseappendStringInfo(es->str, "%s: %s\n", qlabel, value);break;case EXPLAIN_FORMAT_XML:{char *str;appendStringInfoSpaces(es->str, es->indent * 2);ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es);str = escape_xml(value);appendStringInfoString(es->str, str);pfree(str);ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);appendStringInfoChar(es->str, '\n');}break;case EXPLAIN_FORMAT_JSON:ExplainJSONLineEnding(es);appendStringInfoSpaces(es->str, es->indent * 2);escape_json(es->str, qlabel);appendStringInfoString(es->str, ": ");if (numeric)appendStringInfoString(es->str, value);elseescape_json(es->str, value);break;case EXPLAIN_FORMAT_YAML:ExplainYAMLLineStarting(es);appendStringInfo(es->str, "%s: ", qlabel);if (numeric)appendStringInfoString(es->str, value);elseescape_yaml(es->str, value);break;}}/** Explain a string-valued property.*/voidExplainPropertyText(const char *qlabel, const char *value, ExplainState *es){ExplainProperty(qlabel, NULL, value, false, es);}/** Explain an integer-valued property.*/voidExplainPropertyInteger(const char *qlabel, const char *unit, int64 value,ExplainState *es){char buf[32];snprintf(buf, sizeof(buf), INT64_FORMAT, value);ExplainProperty(qlabel, unit, buf, true, es);}/** Explain a float-valued property, using the specified number of* fractional digits.*/voidExplainPropertyFloat(const char *qlabel, const char *unit, double value,int ndigits, ExplainState *es){char *buf;buf = psprintf("%.*f", ndigits, value);ExplainProperty(qlabel, unit, buf, true, es);pfree(buf);}/** Explain a bool-valued property.*/voidExplainPropertyBool(const char *qlabel, bool value, ExplainState *es){ExplainProperty(qlabel, NULL, value ? "true" : "false", true, es);}/** Open a group of related objects.** objtype is the type of the group object, labelname is its label within* a containing object (if any).** If labeled is true, the group members will be labeled properties,* while if it's false, they'll be unlabeled objects.*/voidExplainOpenGroup(const char *objtype, const char *labelname,bool labeled, ExplainState *es){switch (es->format){case EXPLAIN_FORMAT_TEXT:/* nothing to do */break;case EXPLAIN_FORMAT_XML:ExplainXMLTag(objtype, X_OPENING, es);es->indent++;break;case EXPLAIN_FORMAT_JSON:ExplainJSONLineEnding(es);appendStringInfoSpaces(es->str, 2 * es->indent);if (labelname){escape_json(es->str, labelname);appendStringInfoString(es->str, ": ");}appendStringInfoChar(es->str, labeled ? '{' : '[');/** In JSON format, the grouping_stack is an integer list. 0 means* we've emitted nothing at this grouping level, 1 means we've* emitted something (and so the next item needs a comma). See* ExplainJSONLineEnding().*/es->grouping_stack = lcons_int(0, es->grouping_stack);es->indent++;break;case EXPLAIN_FORMAT_YAML:/** In YAML format, the grouping stack is an integer list. 0 means* we've emitted nothing at this grouping level AND this grouping* level is unlabelled and must be marked with "- ". See* ExplainYAMLLineStarting().*/ExplainYAMLLineStarting(es);if (labelname){appendStringInfo(es->str, "%s: ", labelname);es->grouping_stack = lcons_int(1, es->grouping_stack);}else{appendStringInfoString(es->str, "- ");es->grouping_stack = lcons_int(0, es->grouping_stack);}es->indent++;break;}}/** Close a group of related objects.* Parameters must match the corresponding ExplainOpenGroup call.*/voidExplainCloseGroup(const char *objtype, const char *labelname,bool labeled, ExplainState *es){switch (es->format){case EXPLAIN_FORMAT_TEXT:/* nothing to do */break;case EXPLAIN_FORMAT_XML:es->indent--;ExplainXMLTag(objtype, X_CLOSING, es);break;case EXPLAIN_FORMAT_JSON:es->indent--;appendStringInfoChar(es->str, '\n');appendStringInfoSpaces(es->str, 2 * es->indent);appendStringInfoChar(es->str, labeled ? '}' : ']');es->grouping_stack = list_delete_first(es->grouping_stack);break;case EXPLAIN_FORMAT_YAML:es->indent--;es->grouping_stack = list_delete_first(es->grouping_stack);break;}}/** Emit a "dummy" group that never has any members.** objtype is the type of the group object, labelname is its label within* a containing object (if any).*/static voidExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es){switch (es->format){case EXPLAIN_FORMAT_TEXT:/* nothing to do */break;case EXPLAIN_FORMAT_XML:ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es);break;case EXPLAIN_FORMAT_JSON:ExplainJSONLineEnding(es);appendStringInfoSpaces(es->str, 2 * es->indent);if (labelname){escape_json(es->str, labelname);appendStringInfoString(es->str, ": ");}escape_json(es->str, objtype);break;case EXPLAIN_FORMAT_YAML:ExplainYAMLLineStarting(es);if (labelname){escape_yaml(es->str, labelname);appendStringInfoString(es->str, ": ");}else{appendStringInfoString(es->str, "- ");}escape_yaml(es->str, objtype);break;}}/** Emit the start-of-output boilerplate.** This is just enough different from processing a subgroup that we need* a separate pair of subroutines.*/voidExplainBeginOutput(ExplainState *es){switch (es->format){case EXPLAIN_FORMAT_TEXT:/* nothing to do */break;case EXPLAIN_FORMAT_XML:appendStringInfoString(es->str,"<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");es->indent++;break;case EXPLAIN_FORMAT_JSON:/* top-level structure is an array of plans */appendStringInfoChar(es->str, '[');es->grouping_stack = lcons_int(0, es->grouping_stack);es->indent++;break;case EXPLAIN_FORMAT_YAML:es->grouping_stack = lcons_int(0, es->grouping_stack);break;}}/** Emit the end-of-output boilerplate.*/voidExplainEndOutput(ExplainState *es){switch (es->format){case EXPLAIN_FORMAT_TEXT:/* nothing to do */break;case EXPLAIN_FORMAT_XML:es->indent--;appendStringInfoString(es->str, "</explain>");break;case EXPLAIN_FORMAT_JSON:es->indent--;appendStringInfoString(es->str, "\n]");es->grouping_stack = list_delete_first(es->grouping_stack);break;case EXPLAIN_FORMAT_YAML:es->grouping_stack = list_delete_first(es->grouping_stack);break;}}/** Put an appropriate separator between multiple plans*/voidExplainSeparatePlans(ExplainState *es){switch (es->format){case EXPLAIN_FORMAT_TEXT:/* add a blank line */appendStringInfoChar(es->str, '\n');break;case EXPLAIN_FORMAT_XML:case EXPLAIN_FORMAT_JSON:case EXPLAIN_FORMAT_YAML:/* nothing to do */break;}}/** Emit opening or closing XML tag.** "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE.* Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally* add.** XML restricts tag names more than our other output formats, eg they can't* contain white space or slashes. Replace invalid characters with dashes,* so that for example "I/O Read Time" becomes "I-O-Read-Time".*/static voidExplainXMLTag(const char *tagname, int flags, ExplainState *es){const char *s;const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";if ((flags & X_NOWHITESPACE) == 0)appendStringInfoSpaces(es->str, 2 * es->indent);appendStringInfoCharMacro(es->str, '<');if ((flags & X_CLOSING) != 0)appendStringInfoCharMacro(es->str, '/');for (s = tagname; *s; s++)appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-');if ((flags & X_CLOSE_IMMEDIATE) != 0)appendStringInfoString(es->str, " /");appendStringInfoCharMacro(es->str, '>');if ((flags & X_NOWHITESPACE) == 0)appendStringInfoCharMacro(es->str, '\n');}/** Emit a JSON line ending.** JSON requires a comma after each property but the last. To facilitate this,* in JSON format, the text emitted for each property begins just prior to the* preceding line-break (and comma, if applicable).*/static voidExplainJSONLineEnding(ExplainState *es){Assert(es->format == EXPLAIN_FORMAT_JSON);if (linitial_int(es->grouping_stack) != 0)appendStringInfoChar(es->str, ',');elselinitial_int(es->grouping_stack) = 1;appendStringInfoChar(es->str, '\n');}/** Indent a YAML line.** YAML lines are ordinarily indented by two spaces per indentation level.* The text emitted for each property begins just prior to the preceding* line-break, except for the first property in an unlabelled group, for which* it begins immediately after the "- " that introduces the group. The first* property of the group appears on the same line as the opening "- ".*/static voidExplainYAMLLineStarting(ExplainState *es){Assert(es->format == EXPLAIN_FORMAT_YAML);if (linitial_int(es->grouping_stack) == 0){linitial_int(es->grouping_stack) = 1;}else{appendStringInfoChar(es->str, '\n');appendStringInfoSpaces(es->str, es->indent * 2);}}/** YAML is a superset of JSON; unfortunately, the YAML quoting rules are* ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of* http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything.* Empty strings, strings with leading or trailing whitespace, and strings* containing a variety of special characters must certainly be quoted or the* output is invalid; and other seemingly harmless strings like "0xa" or* "true" must be quoted, lest they be interpreted as a hexadecimal or Boolean* constant rather than a string.*/static voidescape_yaml(StringInfo buf, const char *str){escape_json(buf, str);}
参考
https://www.postgresql.org/about/featurematrix/detail/181/
https://www.postgresql.org/docs/12/sql-explain.html
文章转载自CP的PostgreSQL厨房,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




