postgresql/src/backend/commands/explain.c

2377 lines
63 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* explain.c
* Explain query execution plans
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/backend/commands/explain.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
1996-11-06 09:21:43 +01:00
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
#include "executor/hashjoin.h"
#include "foreign/fdwapi.h"
#include "optimizer/clauses.h"
1999-07-16 07:00:38 +02:00
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/json.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/tuplesort.h"
#include "utils/xml.h"
/* Hook for plugins to get control in ExplainOneQuery() */
ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
2007-11-15 22:14:46 +01:00
/* 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 4
static void ExplainOneQuery(Query *query, ExplainState *es,
const char *queryString, ParamListInfo params);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
ExplainState *es);
static double elapsed_time(instr_time *starttime);
static void ExplainNode(PlanState *planstate, List *ancestors,
2010-02-26 03:01:40 +01:00
const char *relationship, const char *plan_name,
ExplainState *es);
static void show_plan_tlist(PlanState *planstate, List *ancestors,
2011-04-10 17:42:00 +02:00
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,
2011-04-10 17:42:00 +02:00
PlanState *planstate, List *ancestors,
bool useprefix, ExplainState *es);
static void show_scan_qual(List *qual, const char *qlabel,
2011-04-10 17:42:00 +02:00
PlanState *planstate, List *ancestors,
ExplainState *es);
static void show_upper_qual(List *qual, const char *qlabel,
2011-04-10 17:42:00 +02:00
PlanState *planstate, List *ancestors,
ExplainState *es);
static void show_sort_keys(SortState *sortstate, List *ancestors,
2011-04-10 17:42:00 +02:00
ExplainState *es);
static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
2011-04-10 17:42:00 +02:00
ExplainState *es);
static void show_sort_keys_common(PlanState *planstate,
2011-04-10 17:42:00 +02:00
int nkeys, AttrNumber *keycols,
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_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
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 ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
static void ExplainSubPlans(List *plans, List *ancestors,
2011-04-10 17:42:00 +02:00
const char *relationship, ExplainState *es);
static void ExplainProperty(const char *qlabel, const char *value,
2010-02-26 03:01:40 +01:00
bool numeric, ExplainState *es);
static void ExplainOpenGroup(const char *objtype, const char *labelname,
bool labeled, ExplainState *es);
static void ExplainCloseGroup(const char *objtype, const char *labelname,
2010-02-26 03:01:40 +01:00
bool labeled, ExplainState *es);
static void ExplainDummyGroup(const char *objtype, const char *labelname,
2010-02-26 03:01:40 +01:00
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
*/
void
ExplainQuery(ExplainStmt *stmt, const char *queryString,
ParamListInfo params, DestReceiver *dest)
{
ExplainState es;
TupOutputState *tstate;
1999-05-25 18:15:34 +02:00
List *rewritten;
ListCell *lc;
bool timing_set = false;
/* Initialize ExplainState. */
ExplainInitState(&es);
/* Parse options list. */
foreach(lc, stmt->options)
{
2010-02-26 03:01:40 +01:00
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, "timing") == 0)
{
timing_set = true;
es.timing = defGetBoolean(opt);
}
else if (strcmp(opt->defname, "format") == 0)
{
2010-02-26 03:01:40 +01:00
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;
else
ereport(ERROR,
2010-02-26 03:01:40 +01:00
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
opt->defname, p)));
}
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized EXPLAIN option \"%s\"",
opt->defname)));
}
if (es.buffers && !es.analyze)
ereport(ERROR,
2010-02-26 03:01:40 +01:00
(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")));
/*
* Parse analysis was done already, but we still have to run the rule
2010-02-26 03:01:40 +01:00
* 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
2007-11-15 22:14:46 +01:00
* 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.
*/
Assert(IsA(stmt->query, Query));
rewritten = QueryRewrite((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((Query *) lfirst(l), &es, queryString, params);
/* 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));
if (es.format == EXPLAIN_FORMAT_TEXT)
do_text_output_multiline(tstate, es.str->data);
else
do_text_output_oneline(tstate, es.str->data);
end_tup_output(tstate);
pfree(es.str->data);
}
/*
* Initialize ExplainState.
*/
void
ExplainInitState(ExplainState *es)
{
/* Set default options. */
memset(es, 0, sizeof(ExplainState));
es->costs = true;
/* Prepare output buffer. */
es->str = makeStringInfo();
}
/*
* ExplainResultDesc -
* construct the result tupledesc for an EXPLAIN
*/
TupleDesc
ExplainResultDesc(ExplainStmt *stmt)
{
TupleDesc tupdesc;
ListCell *lc;
Oid result_type = TEXTOID;
/* Check for XML format option */
foreach(lc, stmt->options)
{
2010-02-26 03:01:40 +01:00
DefElem *opt = (DefElem *) lfirst(lc);
if (strcmp(opt->defname, "format") == 0)
{
2010-02-26 03:01:40 +01:00
char *p = defGetString(opt);
if (strcmp(p, "xml") == 0)
result_type = XMLOID;
else if (strcmp(p, "json") == 0)
result_type = JSONOID;
else
result_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, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
result_type, -1, 0);
return tupdesc;
}
/*
* ExplainOneQuery -
* print out the execution plan for one Query
*/
static void
ExplainOneQuery(Query *query, ExplainState *es,
const char *queryString, ParamListInfo params)
{
/* planner will not cope with utility statements */
if (query->commandType == CMD_UTILITY)
{
ExplainOneUtility(query->utilityStmt, es, queryString, params);
return;
}
/* if an advisor plugin is present, let it manage things */
if (ExplainOneQuery_hook)
(*ExplainOneQuery_hook) (query, es, queryString, params);
else
{
PlannedStmt *plan;
/* plan the query */
plan = pg_plan_query(query, 0, params);
/* run it (if needed) and produce output */
ExplainOnePlan(plan, es, queryString, params);
}
}
/*
* 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)
*
* This is exported because it's called back from prepare.c in the
* EXPLAIN EXECUTE case
*/
void
ExplainOneUtility(Node *utilityStmt, ExplainState *es,
const char *queryString, ParamListInfo params)
{
if (utilityStmt == NULL)
return;
if (IsA(utilityStmt, ExecuteStmt))
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es,
queryString, params);
else if (IsA(utilityStmt, NotifyStmt))
{
if (es->format == EXPLAIN_FORMAT_TEXT)
appendStringInfoString(es->str, "NOTIFY\n");
else
ExplainDummyGroup("Notify", NULL, es);
}
else
{
if (es->format == EXPLAIN_FORMAT_TEXT)
appendStringInfoString(es->str,
2010-02-26 03:01:40 +01:00
"Utility statements have no plan structure\n");
else
ExplainDummyGroup("Utility Statement", NULL, es);
}
}
/*
* ExplainOnePlan -
* given a planned query, execute it if needed, and then print
* EXPLAIN output
*
* Since we ignore any DeclareCursorStmt that might be attached to the query,
* 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.
*
* 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.
*/
void
ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
const char *queryString, ParamListInfo params)
{
QueryDesc *queryDesc;
2005-10-15 04:49:52 +02:00
instr_time starttime;
double totaltime = 0;
int eflags;
int instrument_option = 0;
if (es->analyze && es->timing)
instrument_option |= INSTRUMENT_TIMER;
else if (es->analyze)
instrument_option |= INSTRUMENT_ROWS;
if (es->buffers)
instrument_option |= INSTRUMENT_BUFFERS;
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();
/* Create a QueryDesc requesting no output */
queryDesc = CreateQueryDesc(plannedstmt, queryString,
GetActiveSnapshot(), InvalidSnapshot,
None_Receiver, params, instrument_option);
/* Select execution options */
if (es->analyze)
eflags = 0; /* default run-to-completion flags */
else
eflags = EXEC_FLAG_EXPLAIN_ONLY;
/* call ExecutorStart to prepare the plan for execution */
ExecutorStart(queryDesc, eflags);
/* Execute the plan for statistics if asked for */
if (es->analyze)
{
/* run the plan */
ExecutorRun(queryDesc, ForwardScanDirection, 0L);
/* 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);
/* Print info about runtime of triggers */
if (es->analyze)
{
ResultRelInfo *rInfo;
bool show_relname;
2005-10-15 04:49:52 +02:00
int numrels = queryDesc->estate->es_num_result_relations;
List *targrels = queryDesc->estate->es_trig_target_relations;
2005-10-15 04:49:52 +02:00
int nr;
ListCell *l;
ExplainOpenGroup("Triggers", "Triggers", false, es);
show_relname = (numrels > 1 || targrels != NIL);
rInfo = queryDesc->estate->es_result_relations;
for (nr = 0; nr < numrels; rInfo++, nr++)
report_triggers(rInfo, show_relname, es);
foreach(l, targrels)
{
rInfo = (ResultRelInfo *) lfirst(l);
report_triggers(rInfo, show_relname, es);
}
ExplainCloseGroup("Triggers", "Triggers", false, es);
}
/*
2005-10-15 04:49:52 +02:00
* Close down the query and free resources. Include time for this in the
* total runtime (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);
if (es->analyze)
{
if (es->format == EXPLAIN_FORMAT_TEXT)
appendStringInfo(es->str, "Total runtime: %.3f ms\n",
1000.0 * totaltime);
else
ExplainPropertyFloat("Total Runtime", 1000.0 * totaltime,
3, es);
}
ExplainCloseGroup("Query", NULL, true, 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
2010-02-26 03:01:40 +01:00
* initializing the output buffer es->str. Other fields in *es are
* initialized here.
*
* NB: will not work on utility statements
*/
void
ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
{
Assert(queryDesc->plannedstmt != NULL);
es->pstmt = queryDesc->plannedstmt;
es->rtable = queryDesc->plannedstmt->rtable;
ExplainNode(queryDesc->planstate, NIL, NULL, NULL, es);
}
/*
* ExplainQueryText -
2010-02-26 03:01:40 +01:00
* 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
2010-02-26 03:01:40 +01:00
* initializing the output buffer es->str.
*
*/
void
ExplainQueryText(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 void
report_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);
/*
2007-11-15 22:14:46 +01:00
* 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
2010-02-26 03:01:40 +01:00
* 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);
else
appendStringInfoString(es->str, "Trigger");
if (conname)
appendStringInfo(es->str, " for constraint %s", conname);
if (show_relname)
appendStringInfo(es->str, " on %s", relname);
appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",
1000.0 * instr->total, instr->ntuples);
}
else
{
ExplainPropertyText("Trigger Name", trig->tgname, es);
if (conname)
ExplainPropertyText("Constraint Name", conname, es);
ExplainPropertyText("Relation", relname, es);
ExplainPropertyFloat("Time", 1000.0 * instr->total, 3, es);
ExplainPropertyFloat("Calls", instr->ntuples, 0, es);
}
if (conname)
pfree(conname);
ExplainCloseGroup("Trigger", NULL, true, es);
}
}
/* Compute elapsed time in seconds since given timestamp */
static double
elapsed_time(instr_time *starttime)
{
2005-10-15 04:49:52 +02:00
instr_time endtime;
INSTR_TIME_SET_CURRENT(endtime);
INSTR_TIME_SUBTRACT(endtime, *starttime);
return INSTR_TIME_GET_DOUBLE(endtime);
}
/*
* 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 void
ExplainNode(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 *operation = NULL;
int save_indent = es->indent;
bool haschildren;
switch (nodeTag(plan))
{
case T_Result:
pname = sname = "Result";
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:
2010-02-26 03:01:40 +01:00
pname = "Merge"; /* "Join" gets added by jointype switch */
sname = "Merge Join";
break;
case T_HashJoin:
2010-02-26 03:01:40 +01:00
pname = "Hash"; /* "Join" gets added by jointype switch */
sname = "Hash Join";
break;
case T_SeqScan:
pname = sname = "Seq Scan";
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_ValuesScan:
pname = sname = "Values Scan";
break;
case T_CteScan:
pname = sname = "CTE Scan";
break;
case T_WorkTableScan:
pname = sname = "WorkTable Scan";
break;
case T_ForeignScan:
pname = sname = "Foreign Scan";
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:
sname = "Aggregate";
switch (((Agg *) plan)->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;
default:
pname = "Aggregate ???";
strategy = "???";
break;
}
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;
}
appendStringInfoString(es->str, pname);
es->indent++;
}
else
{
ExplainPropertyText("Node Type", sname, es);
if (strategy)
ExplainPropertyText("Strategy", strategy, es);
if (operation)
ExplainPropertyText("Operation", operation, es);
if (relationship)
ExplainPropertyText("Parent Relationship", relationship, es);
if (plan_name)
ExplainPropertyText("Subplan Name", plan_name, es);
}
switch (nodeTag(plan))
{
case T_SeqScan:
case T_BitmapHeapScan:
case T_TidScan:
case T_SubqueryScan:
case T_FunctionScan:
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
case T_ForeignScan:
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 =
2010-02-26 03:01:40 +01:00
explain_get_index_name(bitmapindexscan->indexid);
if (es->format == EXPLAIN_FORMAT_TEXT)
appendStringInfo(es->str, " on %s", indexname);
else
ExplainPropertyText("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))
appendStringInfo(es->str, " Join");
}
else
ExplainPropertyText("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);
else
ExplainPropertyText("Command", setopcmd, es);
}
break;
default:
break;
}
2005-10-15 04:49:52 +02:00
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", plan->startup_cost, 2, es);
ExplainPropertyFloat("Total Cost", plan->total_cost, 2, es);
ExplainPropertyFloat("Plan Rows", plan->plan_rows, 0, es);
ExplainPropertyInteger("Plan Width", plan->plan_width, es);
}
}
/*
2005-10-15 04:49:52 +02:00
* We have to forcibly clean up the instrumentation state because we
* haven't done ExecutorEnd yet. This is pretty grotty ...
*/
if (planstate->instrument)
InstrEndLoop(planstate->instrument);
if (planstate->instrument && planstate->instrument->nloops > 0)
{
double nloops = planstate->instrument->nloops;
double startup_sec = 1000.0 * planstate->instrument->startup / nloops;
double total_sec = 1000.0 * planstate->instrument->total / nloops;
double rows = planstate->instrument->ntuples / nloops;
if (es->format == EXPLAIN_FORMAT_TEXT)
{
if (planstate->instrument->need_timer)
appendStringInfo(es->str,
" (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
startup_sec, total_sec, rows, nloops);
else
appendStringInfo(es->str,
" (actual rows=%.0f loops=%.0f)",
rows, nloops);
}
else
{
if (planstate->instrument->need_timer)
{
ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
}
ExplainPropertyFloat("Actual Rows", rows, 0, es);
ExplainPropertyFloat("Actual Loops", nloops, 0, es);
}
}
else if (es->analyze)
{
if (es->format == EXPLAIN_FORMAT_TEXT)
appendStringInfo(es->str, " (never executed)");
else if (planstate->instrument->need_timer)
{
ExplainPropertyFloat("Actual Startup Time", 0.0, 3, es);
ExplainPropertyFloat("Actual Total Time", 0.0, 3, es);
}
else
{
ExplainPropertyFloat("Actual Rows", 0.0, 0, es);
ExplainPropertyFloat("Actual Loops", 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);
/* 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)
ExplainPropertyLong("Heap Fetches",
((IndexOnlyScanState *) planstate)->ioss_HeapFetches, 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);
/* FALL THRU */
case T_SeqScan:
case T_ValuesScan:
case T_CteScan:
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_FunctionScan:
if (es->verbose)
show_expression(((FunctionScan *) plan)->funcexpr,
"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.
*/
2006-10-04 02:30:14 +02:00
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_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:
case T_Group:
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((SortState *) planstate, ancestors, es);
show_sort_info((SortState *) planstate, es);
break;
case T_MergeAppend:
show_merge_append_keys((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_Hash:
show_hash_info((HashState *) planstate, es);
break;
default:
break;
}
/* Show buffer usage */
if (es->buffers)
{
const BufferUsage *usage = &planstate->instrument->bufusage;
if (es->format == EXPLAIN_FORMAT_TEXT)
{
2010-02-26 03:01:40 +01:00
bool has_shared = (usage->shared_blks_hit > 0 ||
usage->shared_blks_read > 0 ||
usage->shared_blks_dirtied > 0 ||
usage->shared_blks_written > 0);
2010-02-26 03:01:40 +01:00
bool has_local = (usage->local_blks_hit > 0 ||
usage->local_blks_read > 0 ||
usage->local_blks_dirtied > 0 ||
usage->local_blks_written > 0);
2010-02-26 03:01:40 +01:00
bool has_temp = (usage->temp_blks_read > 0 ||
usage->temp_blks_written > 0);
/* 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",
2010-02-26 03:01:40 +01:00
usage->shared_blks_hit);
if (usage->shared_blks_read > 0)
appendStringInfo(es->str, " read=%ld",
2010-02-26 03:01:40 +01:00
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",
2010-02-26 03:01:40 +01:00
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",
2010-02-26 03:01:40 +01:00
usage->local_blks_hit);
if (usage->local_blks_read > 0)
appendStringInfo(es->str, " read=%ld",
2010-02-26 03:01:40 +01:00
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",
2010-02-26 03:01:40 +01:00
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",
2010-02-26 03:01:40 +01:00
usage->temp_blks_read);
if (usage->temp_blks_written > 0)
appendStringInfo(es->str, " written=%ld",
2010-02-26 03:01:40 +01:00
usage->temp_blks_written);
}
appendStringInfoChar(es->str, '\n');
}
}
else
{
ExplainPropertyLong("Shared Hit Blocks", usage->shared_blks_hit, es);
ExplainPropertyLong("Shared Read Blocks", usage->shared_blks_read, es);
ExplainPropertyLong("Shared Dirtied Blocks", usage->shared_blks_dirtied, es);
ExplainPropertyLong("Shared Written Blocks", usage->shared_blks_written, es);
ExplainPropertyLong("Local Hit Blocks", usage->local_blks_hit, es);
ExplainPropertyLong("Local Read Blocks", usage->local_blks_read, es);
ExplainPropertyLong("Local Dirtied Blocks", usage->local_blks_dirtied, es);
ExplainPropertyLong("Local Written Blocks", usage->local_blks_written, es);
ExplainPropertyLong("Temp Read Blocks", usage->temp_blks_read, es);
ExplainPropertyLong("Temp Written Blocks", usage->temp_blks_written, es);
}
}
/* 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) ||
planstate->subPlan;
if (haschildren)
{
ExplainOpenGroup("Plans", "Plans", false, es);
/* Pass current PlanState as head of ancestors list for children */
ancestors = lcons(planstate, ancestors);
}
1998-02-13 04:21:30 +01:00
/* 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(((ModifyTable *) plan)->plans,
((ModifyTableState *) planstate)->mt_plans,
ancestors, es);
break;
case T_Append:
ExplainMemberNodes(((Append *) plan)->appendplans,
((AppendState *) planstate)->appendplans,
ancestors, es);
break;
case T_MergeAppend:
ExplainMemberNodes(((MergeAppend *) plan)->mergeplans,
((MergeAppendState *) planstate)->mergeplans,
ancestors, es);
break;
case T_BitmapAnd:
ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
((BitmapAndState *) planstate)->bitmapplans,
ancestors, es);
break;
case T_BitmapOr:
ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
((BitmapOrState *) planstate)->bitmapplans,
ancestors, es);
break;
case T_SubqueryScan:
ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,
"Subquery", NULL, 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 void
show_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;
/* Set up deparsing context */
context = deparse_context_for_planstate((Node *) planstate,
ancestors,
es->rtable);
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,
2010-02-26 03:01:40 +01:00
deparse_expression((Node *) tle->expr, context,
useprefix, false));
}
/* Print results */
ExplainPropertyList("Output", result, es);
}
/*
* Show a generic expression
*/
static void
show_expression(Node *node, const char *qlabel,
PlanState *planstate, List *ancestors,
bool useprefix, ExplainState *es)
{
List *context;
char *exprstr;
/* Set up deparsing context */
context = deparse_context_for_planstate((Node *) planstate,
ancestors,
es->rtable);
/* 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 void
show_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 void
show_scan_qual(List *qual, const char *qlabel,
PlanState *planstate, List *ancestors,
ExplainState *es)
{
bool useprefix;
2011-04-10 17:42:00 +02:00
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 void
show_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 void
show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
{
Sort *plan = (Sort *) sortstate->ss.ps.plan;
show_sort_keys_common((PlanState *) sortstate,
plan->numCols, plan->sortColIdx,
ancestors, es);
}
/*
* Likewise, for a MergeAppend node.
*/
static void
show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
ExplainState *es)
{
MergeAppend *plan = (MergeAppend *) mstate->ps.plan;
show_sort_keys_common((PlanState *) mstate,
plan->numCols, plan->sortColIdx,
ancestors, es);
}
static void
show_sort_keys_common(PlanState *planstate, int nkeys, AttrNumber *keycols,
List *ancestors, ExplainState *es)
{
Plan *plan = planstate->plan;
List *context;
List *result = NIL;
bool useprefix;
int keyno;
char *exprstr;
if (nkeys <= 0)
return;
/* Set up deparsing context */
context = deparse_context_for_planstate((Node *) planstate,
ancestors,
es->rtable);
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);
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);
}
ExplainPropertyList("Sort Key", result, es);
}
/*
* If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
*/
static void
show_sort_info(SortState *sortstate, ExplainState *es)
{
Assert(IsA(sortstate, SortState));
if (es->analyze && sortstate->sort_Done &&
sortstate->tuplesortstate != NULL)
{
2010-02-26 03:01:40 +01:00
Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;
const char *sortMethod;
const char *spaceType;
long spaceUsed;
tuplesort_get_stats(state, &sortMethod, &spaceType, &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);
ExplainPropertyLong("Sort Space Used", spaceUsed, es);
ExplainPropertyText("Sort Space Type", spaceType, es);
}
}
}
/*
* Show information on hash buckets/batches.
*/
static void
show_hash_info(HashState *hashstate, ExplainState *es)
{
HashJoinTable hashtable;
Assert(IsA(hashstate, HashState));
hashtable = hashstate->hashtable;
if (hashtable)
{
2010-02-26 03:01:40 +01:00
long spacePeakKb = (hashtable->spacePeak + 1023) / 1024;
if (es->format != EXPLAIN_FORMAT_TEXT)
{
ExplainPropertyLong("Hash Buckets", hashtable->nbuckets, es);
ExplainPropertyLong("Hash Batches", hashtable->nbatch, es);
ExplainPropertyLong("Original Hash Batches",
hashtable->nbatch_original, es);
ExplainPropertyLong("Peak Memory Usage", spacePeakKb, es);
}
else if (hashtable->nbatch_original != hashtable->nbatch)
{
appendStringInfoSpaces(es->str, es->indent * 2);
appendStringInfo(es->str,
2010-02-26 03:01:40 +01:00
"Buckets: %d Batches: %d (originally %d) Memory Usage: %ldkB\n",
hashtable->nbuckets, hashtable->nbatch,
hashtable->nbatch_original, spacePeakKb);
}
else
{
appendStringInfoSpaces(es->str, es->indent * 2);
appendStringInfo(es->str,
2010-02-26 03:01:40 +01:00
"Buckets: %d Batches: %d Memory Usage: %ldkB\n",
hashtable->nbuckets, hashtable->nbatch,
spacePeakKb);
}
}
}
/*
* If it's EXPLAIN ANALYZE, show instrumentation information for a plan node
*
* "which" identifies which instrumentation counter to print
*/
static void
show_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;
else
nfiltered = 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, nfiltered / nloops, 0, es);
else
ExplainPropertyFloat(qlabel, 0.0, 0, es);
}
}
/*
* Show extra information for a ForeignScan node.
*/
static void
show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
{
FdwRoutine *fdwroutine = fsstate->fdwroutine;
/* Let the FDW emit whatever fields it wants */
fdwroutine->ExplainForeignScan(fsstate, 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.
*/
static const char *
explain_get_index_name(Oid indexId)
{
2007-11-15 22:14:46 +01:00
const char *result;
if (explain_get_index_name_hook)
result = (*explain_get_index_name_hook) (indexId);
else
result = NULL;
if (result == NULL)
{
/* default behavior: look in the catalogs and quote it */
result = get_rel_name(indexId);
if (result == NULL)
elog(ERROR, "cache lookup failed for index %u", indexId);
result = quote_identifier(result);
}
return result;
}
/*
* Add some additional details about an IndexScan or IndexOnlyScan
*/
static void
ExplainIndexScanDetails(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", 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 void
ExplainScanTarget(Scan *plan, ExplainState *es)
{
ExplainTargetRel((Plan *) plan, plan->scanrelid, es);
}
/*
* Show the target of a ModifyTable node
*/
static void
ExplainModifyTarget(ModifyTable *plan, ExplainState *es)
{
Index rti;
/*
* We show the name of the first target relation. In multi-target-table
* cases this should always be the parent of the inheritance tree.
*/
Assert(plan->resultRelations != NIL);
rti = linitial_int(plan->resultRelations);
ExplainTargetRel((Plan *) plan, rti, es);
}
/*
* Show the target relation of a scan or modify node
*/
static void
ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
{
char *objectname = NULL;
char *namespace = NULL;
const char *objecttag = NULL;
RangeTblEntry *rte;
rte = rt_fetch(rti, es->rtable);
switch (nodeTag(plan))
{
case T_SeqScan:
case T_IndexScan:
case T_IndexOnlyScan:
case T_BitmapHeapScan:
case T_TidScan:
case T_ForeignScan:
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:
{
Node *funcexpr;
/* Assert it's on a RangeFunction */
Assert(rte->rtekind == RTE_FUNCTION);
/*
* If the expression is still a function call, we can get the
* real name of the function. Otherwise, punt (this can
* happen if the optimizer simplified away the function call,
* for example).
*/
funcexpr = ((FunctionScan *) plan)->funcexpr;
if (funcexpr && IsA(funcexpr, FuncExpr))
{
Oid funcid = ((FuncExpr *) funcexpr)->funcid;
objectname = get_func_name(funcid);
if (es->verbose)
namespace =
get_namespace_name(get_func_namespace(funcid));
}
objecttag = "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_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(rte->eref->aliasname, objectname) != 0)
appendStringInfo(es->str, " %s",
quote_identifier(rte->eref->aliasname));
}
else
{
if (objecttag != NULL && objectname != NULL)
ExplainPropertyText(objecttag, objectname, es);
if (namespace != NULL)
ExplainPropertyText("Schema", namespace, es);
ExplainPropertyText("Alias", rte->eref->aliasname, 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.
*
* Note: we don't actually need to examine the Plan list members, but
* we need the list in order to determine the length of the PlanState array.
*/
static void
ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es)
{
int nplans = list_length(plans);
int j;
for (j = 0; j < nplans; j++)
ExplainNode(planstates[j], ancestors,
"Member", NULL, 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 void
ExplainSubPlans(List *plans, List *ancestors,
const char *relationship, ExplainState *es)
{
ListCell *lst;
foreach(lst, plans)
{
SubPlanState *sps = (SubPlanState *) lfirst(lst);
SubPlan *sp = (SubPlan *) sps->xprstate.expr;
ExplainNode(sps->planstate, ancestors,
relationship, sp->plan_name, 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.
*/
void
ExplainPropertyList(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)
{
2010-02-26 03:01:40 +01:00
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 simple property.
*
* If "numeric" is true, the value is a number (or other value that
* doesn't need quoting in JSON).
*
* This usually should not be invoked directly, but via one of the datatype
* specific routines ExplainPropertyText, ExplainPropertyInteger, etc.
*/
static void
ExplainProperty(const char *qlabel, const char *value, bool numeric,
ExplainState *es)
{
switch (es->format)
{
case EXPLAIN_FORMAT_TEXT:
appendStringInfoSpaces(es->str, es->indent * 2);
appendStringInfo(es->str, "%s: %s\n", qlabel, value);
break;
case EXPLAIN_FORMAT_XML:
{
2010-02-26 03:01:40 +01:00
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);
else
escape_json(es->str, value);
break;
case EXPLAIN_FORMAT_YAML:
ExplainYAMLLineStarting(es);
appendStringInfo(es->str, "%s: ", qlabel);
if (numeric)
appendStringInfoString(es->str, value);
else
escape_yaml(es->str, value);
break;
}
}
/*
* Explain a string-valued property.
*/
void
ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
{
ExplainProperty(qlabel, value, false, es);
}
/*
* Explain an integer-valued property.
*/
void
ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es)
{
2010-02-26 03:01:40 +01:00
char buf[32];
snprintf(buf, sizeof(buf), "%d", value);
ExplainProperty(qlabel, buf, true, es);
}
/*
* Explain a long-integer-valued property.
*/
void
ExplainPropertyLong(const char *qlabel, long value, ExplainState *es)
{
2010-02-26 03:01:40 +01:00
char buf[32];
snprintf(buf, sizeof(buf), "%ld", value);
ExplainProperty(qlabel, buf, true, es);
}
/*
* Explain a float-valued property, using the specified number of
* fractional digits.
*/
void
ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
ExplainState *es)
{
2010-02-26 03:01:40 +01:00
char buf[256];
snprintf(buf, sizeof(buf), "%.*f", ndigits, value);
ExplainProperty(qlabel, buf, 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.
*/
static void
ExplainOpenGroup(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
2010-02-26 03:01:40 +01:00
* 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.
*/
static void
ExplainCloseGroup(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 void
ExplainDummyGroup(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.
*/
void
ExplainBeginOutput(ExplainState *es)
{
switch (es->format)
{
case EXPLAIN_FORMAT_TEXT:
/* nothing to do */
break;
case EXPLAIN_FORMAT_XML:
appendStringInfoString(es->str,
2010-02-26 03:01:40 +01:00
"<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.
*/
void
ExplainEndOutput(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
*/
void
ExplainSeparatePlans(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 tag names can't contain white space, so we replace any spaces in
* "tagname" with dashes.
*/
static void
ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
{
const char *s;
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++)
appendStringInfoCharMacro(es->str, (*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.
*
2010-02-26 03:01:40 +01:00
* 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 void
ExplainJSONLineEnding(ExplainState *es)
{
Assert(es->format == EXPLAIN_FORMAT_JSON);
if (linitial_int(es->grouping_stack) != 0)
appendStringInfoChar(es->str, ',');
else
linitial_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
2010-02-26 03:01:40 +01:00
* it begins immediately after the "- " that introduces the group. The first
* property of the group appears on the same line as the opening "- ".
*/
static void
ExplainYAMLLineStarting(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; unfortuantely, 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 void
escape_yaml(StringInfo buf, const char *str)
{
escape_json(buf, str);
}