/*-------------------------------------------------------------------------
 *
 * explain.c
 *	  Explain query execution plans
 *
 * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994-5, Regents of the University of California
 *
 * IDENTIFICATION
 *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.188 2009/07/26 23:34:17 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/xact.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/explain.h"
#include "commands/prepare.h"
#include "commands/trigger.h"
#include "executor/instrument.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
#include "optimizer/var.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/tuplesort.h"
#include "utils/snapmgr.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;


static void ExplainOneQuery(Query *query, ExplainState *es,
				const char *queryString, ParamListInfo params);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
				StringInfo buf);
static double elapsed_time(instr_time *starttime);
static void ExplainNode(Plan *plan, PlanState *planstate,
				Plan *outer_plan, int indent, ExplainState *es);
static void show_plan_tlist(Plan *plan, int indent, ExplainState *es);
static void show_qual(List *qual, const char *qlabel, Plan *plan,
		  Plan *outer_plan, int indent, bool useprefix, ExplainState *es);
static void show_scan_qual(List *qual, const char *qlabel,
			   Plan *scan_plan, Plan *outer_plan,
			   int indent, ExplainState *es);
static void show_upper_qual(List *qual, const char *qlabel, Plan *plan,
				int indent, ExplainState *es);
static void show_sort_keys(Plan *sortplan, int indent, ExplainState *es);
static void show_sort_info(SortState *sortstate, int indent, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void ExplainScanTarget(Scan *plan, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstate,
				   Plan *outer_plan, int indent, ExplainState *es);
static void ExplainSubPlans(List *plans, int indent, ExplainState *es);


/*
 * ExplainQuery -
 *	  execute an EXPLAIN command
 */
void
ExplainQuery(ExplainStmt *stmt, const char *queryString,
			 ParamListInfo params, DestReceiver *dest)
{
	ExplainState es;
	Oid		   *param_types;
	int			num_params;
	TupOutputState *tstate;
	List	   *rewritten;
	ListCell   *lc;

	/* Initialize ExplainState. */
	ExplainInitState(&es);

	/* 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
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("unrecognized EXPLAIN option \"%s\"",
							opt->defname)));
	}

	/* Convert parameter type data to the form parser wants */
	getParamListTypes(params, &param_types, &num_params);

	/*
	 * Run parse analysis and rewrite.	Note this also acquires sufficient
	 * locks on the source table(s).
	 *
	 * Because the parser and planner tend to scribble on their 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 = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
									   queryString, param_types, num_params);

	if (rewritten == NIL)
	{
		/* In the case of an INSTEAD NOTHING, tell at least that */
		appendStringInfoString(es.str, "Query rewrites to nothing\n");
	}
	else
	{
		ListCell   *l;

		/* Explain every plan */
		foreach(l, rewritten)
		{
			ExplainOneQuery((Query *) lfirst(l), &es, queryString, params);
			/* put a blank line between plans */
			if (lnext(l) != NULL)
				appendStringInfoChar(es.str, '\n');
		}
	}

	/* output tuples */
	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
	do_text_output_multiline(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;

	/* need a tuple descriptor representing a single TEXT column */
	tupdesc = CreateTemplateTupleDesc(1, false);
	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
					   TEXTOID, -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))
		appendStringInfoString(es->str, "NOTIFY\n");
	else
		appendStringInfoString(es->str,
							   "Utility statements have no plan structure\n");
}

/*
 * 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;
	instr_time	starttime;
	double		totaltime = 0;
	int			eflags;

	/*
	 * Use a snapshot with an updated command ID to ensure this query sees
	 * results of any previously executed queries.
	 */
	PushUpdatedSnapshot(GetActiveSnapshot());

	/* Create a QueryDesc requesting no output */
	queryDesc = CreateQueryDesc(plannedstmt, queryString,
								GetActiveSnapshot(), InvalidSnapshot,
								None_Receiver, params, es->analyze);

	INSTR_TIME_SET_CURRENT(starttime);

	/* If analyzing, we need to cope with queued triggers */
	if (es->analyze)
		AfterTriggerBeginQuery();

	/* 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);

		/* We can't clean up 'till we're done printing the stats... */
		totaltime += elapsed_time(&starttime);
	}

	/* Create textual dump of plan tree */
	ExplainPrintPlan(es, queryDesc);

	/*
	 * If we ran the command, run any AFTER triggers it queued.  (Note this
	 * will not include DEFERRED triggers; since those don't run until end of
	 * transaction, we can't measure them.)  Include into total runtime.
	 */
	if (es->analyze)
	{
		INSTR_TIME_SET_CURRENT(starttime);
		AfterTriggerEndQuery(queryDesc->estate);
		totaltime += elapsed_time(&starttime);
	}

	/* Print info about runtime of triggers */
	if (es->analyze)
	{
		ResultRelInfo *rInfo;
		bool		show_relname;
		int			numrels = queryDesc->estate->es_num_result_relations;
		List	   *targrels = queryDesc->estate->es_trig_target_relations;
		int			nr;
		ListCell   *l;

		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->str);

		foreach(l, targrels)
		{
			rInfo = (ResultRelInfo *) lfirst(l);
			report_triggers(rInfo, show_relname, es->str);
		}
	}

	/*
	 * 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)
		appendStringInfo(es->str, "Total runtime: %.3f ms\n",
						 1000.0 * totaltime);
}

/*
 * 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.  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->plannedstmt->planTree, queryDesc->planstate,
				NULL, 0, es);
}

/*
 * report_triggers -
 *		report execution stats for a single relation's triggers
 */
static void
report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf)
{
	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	   *conname;

		/* 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;

		if (OidIsValid(trig->tgconstraint) &&
			(conname = get_constraint_name(trig->tgconstraint)) != NULL)
		{
			appendStringInfo(buf, "Trigger for constraint %s", conname);
			pfree(conname);
		}
		else
			appendStringInfo(buf, "Trigger %s", trig->tgname);

		if (show_relname)
			appendStringInfo(buf, " on %s",
							 RelationGetRelationName(rInfo->ri_RelationDesc));

		appendStringInfo(buf, ": time=%.3f calls=%.0f\n",
						 1000.0 * instr->total, instr->ntuples);
	}
}

/* Compute elapsed time in seconds since given timestamp */
static double
elapsed_time(instr_time *starttime)
{
	instr_time	endtime;

	INSTR_TIME_SET_CURRENT(endtime);
	INSTR_TIME_SUBTRACT(endtime, *starttime);
	return INSTR_TIME_GET_DOUBLE(endtime);
}

/*
 * ExplainNode -
 *	  converts a Plan node into ascii string and appends it to es->str
 *
 * planstate points to the executor state node corresponding to the plan node.
 * We need this to get at the instrumentation data (if any) as well as the
 * list of subplans.
 *
 * outer_plan, if not null, references another plan node that is the outer
 * side of a join with the current node.  This is only interesting for
 * deciphering runtime keys of an inner indexscan.
 *
 * If indent is positive, we indent the plan output accordingly and put "->"
 * in front of it.  This should only happen for child plan nodes.
 */
static void
ExplainNode(Plan *plan, PlanState *planstate,
			Plan *outer_plan,
			int indent, ExplainState *es)
{
	const char *pname;

	if (indent)
	{
		Assert(indent >= 2);
		appendStringInfoSpaces(es->str, 2 * indent - 4);
		appendStringInfoString(es->str, "->  ");
	}

	if (plan == NULL)
	{
		appendStringInfoChar(es->str, '\n');
		return;
	}

	switch (nodeTag(plan))
	{
		case T_Result:
			pname = "Result";
			break;
		case T_Append:
			pname = "Append";
			break;
		case T_RecursiveUnion:
			pname = "Recursive Union";
			break;
		case T_BitmapAnd:
			pname = "BitmapAnd";
			break;
		case T_BitmapOr:
			pname = "BitmapOr";
			break;
		case T_NestLoop:
			switch (((NestLoop *) plan)->join.jointype)
			{
				case JOIN_INNER:
					pname = "Nested Loop";
					break;
				case JOIN_LEFT:
					pname = "Nested Loop Left Join";
					break;
				case JOIN_FULL:
					pname = "Nested Loop Full Join";
					break;
				case JOIN_RIGHT:
					pname = "Nested Loop Right Join";
					break;
				case JOIN_SEMI:
					pname = "Nested Loop Semi Join";
					break;
				case JOIN_ANTI:
					pname = "Nested Loop Anti Join";
					break;
				default:
					pname = "Nested Loop ??? Join";
					break;
			}
			break;
		case T_MergeJoin:
			switch (((MergeJoin *) plan)->join.jointype)
			{
				case JOIN_INNER:
					pname = "Merge Join";
					break;
				case JOIN_LEFT:
					pname = "Merge Left Join";
					break;
				case JOIN_FULL:
					pname = "Merge Full Join";
					break;
				case JOIN_RIGHT:
					pname = "Merge Right Join";
					break;
				case JOIN_SEMI:
					pname = "Merge Semi Join";
					break;
				case JOIN_ANTI:
					pname = "Merge Anti Join";
					break;
				default:
					pname = "Merge ??? Join";
					break;
			}
			break;
		case T_HashJoin:
			switch (((HashJoin *) plan)->join.jointype)
			{
				case JOIN_INNER:
					pname = "Hash Join";
					break;
				case JOIN_LEFT:
					pname = "Hash Left Join";
					break;
				case JOIN_FULL:
					pname = "Hash Full Join";
					break;
				case JOIN_RIGHT:
					pname = "Hash Right Join";
					break;
				case JOIN_SEMI:
					pname = "Hash Semi Join";
					break;
				case JOIN_ANTI:
					pname = "Hash Anti Join";
					break;
				default:
					pname = "Hash ??? Join";
					break;
			}
			break;
		case T_SeqScan:
			pname = "Seq Scan";
			break;
		case T_IndexScan:
			pname = "Index Scan";
			break;
		case T_BitmapIndexScan:
			pname = "Bitmap Index Scan";
			break;
		case T_BitmapHeapScan:
			pname = "Bitmap Heap Scan";
			break;
		case T_TidScan:
			pname = "Tid Scan";
			break;
		case T_SubqueryScan:
			pname = "Subquery Scan";
			break;
		case T_FunctionScan:
			pname = "Function Scan";
			break;
		case T_ValuesScan:
			pname = "Values Scan";
			break;
		case T_CteScan:
			pname = "CTE Scan";
			break;
		case T_WorkTableScan:
			pname = "WorkTable Scan";
			break;
		case T_Material:
			pname = "Materialize";
			break;
		case T_Sort:
			pname = "Sort";
			break;
		case T_Group:
			pname = "Group";
			break;
		case T_Agg:
			switch (((Agg *) plan)->aggstrategy)
			{
				case AGG_PLAIN:
					pname = "Aggregate";
					break;
				case AGG_SORTED:
					pname = "GroupAggregate";
					break;
				case AGG_HASHED:
					pname = "HashAggregate";
					break;
				default:
					pname = "Aggregate ???";
					break;
			}
			break;
		case T_WindowAgg:
			pname = "WindowAgg";
			break;
		case T_Unique:
			pname = "Unique";
			break;
		case T_SetOp:
			switch (((SetOp *) plan)->strategy)
			{
				case SETOP_SORTED:
					switch (((SetOp *) plan)->cmd)
					{
						case SETOPCMD_INTERSECT:
							pname = "SetOp Intersect";
							break;
						case SETOPCMD_INTERSECT_ALL:
							pname = "SetOp Intersect All";
							break;
						case SETOPCMD_EXCEPT:
							pname = "SetOp Except";
							break;
						case SETOPCMD_EXCEPT_ALL:
							pname = "SetOp Except All";
							break;
						default:
							pname = "SetOp ???";
							break;
					}
					break;
				case SETOP_HASHED:
					switch (((SetOp *) plan)->cmd)
					{
						case SETOPCMD_INTERSECT:
							pname = "HashSetOp Intersect";
							break;
						case SETOPCMD_INTERSECT_ALL:
							pname = "HashSetOp Intersect All";
							break;
						case SETOPCMD_EXCEPT:
							pname = "HashSetOp Except";
							break;
						case SETOPCMD_EXCEPT_ALL:
							pname = "HashSetOp Except All";
							break;
						default:
							pname = "HashSetOp ???";
							break;
					}
					break;
				default:
					pname = "SetOp ???";
					break;
			}
			break;
		case T_Limit:
			pname = "Limit";
			break;
		case T_Hash:
			pname = "Hash";
			break;
		default:
			pname = "???";
			break;
	}

	appendStringInfoString(es->str, pname);
	switch (nodeTag(plan))
	{
		case T_IndexScan:
			if (ScanDirectionIsBackward(((IndexScan *) plan)->indexorderdir))
				appendStringInfoString(es->str, " Backward");
			appendStringInfo(es->str, " using %s",
					  explain_get_index_name(((IndexScan *) plan)->indexid));
			/* FALL THRU */
		case T_SeqScan:
		case T_BitmapHeapScan:
		case T_TidScan:
		case T_SubqueryScan:
		case T_FunctionScan:
		case T_ValuesScan:
		case T_CteScan:
		case T_WorkTableScan:
			ExplainScanTarget((Scan *) plan, es);
			break;
		case T_BitmapIndexScan:
			appendStringInfo(es->str, " on %s",
				explain_get_index_name(((BitmapIndexScan *) plan)->indexid));
			break;
		default:
			break;
	}

	if (es->costs)
		appendStringInfo(es->str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
						 plan->startup_cost, plan->total_cost,
						 plan->plan_rows, plan->plan_width);

	/*
	 * 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;

		appendStringInfo(es->str,
						 " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
						 1000.0 * planstate->instrument->startup / nloops,
						 1000.0 * planstate->instrument->total / nloops,
						 planstate->instrument->ntuples / nloops,
						 planstate->instrument->nloops);
	}
	else if (es->analyze)
		appendStringInfoString(es->str, " (never executed)");
	appendStringInfoChar(es->str, '\n');

	/* target list */
	if (es->verbose)
		show_plan_tlist(plan, indent, es);

	/* quals, sort keys, etc */
	switch (nodeTag(plan))
	{
		case T_IndexScan:
			show_scan_qual(((IndexScan *) plan)->indexqualorig,
						   "Index Cond", plan, outer_plan, indent, es);
			show_scan_qual(plan->qual,
						   "Filter", plan, outer_plan, indent, es);
			break;
		case T_BitmapIndexScan:
			show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
						   "Index Cond", plan, outer_plan, indent, es);
			break;
		case T_BitmapHeapScan:
			show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
						   "Recheck Cond", plan, outer_plan, indent, es);
			/* FALL THRU */
		case T_SeqScan:
		case T_FunctionScan:
		case T_ValuesScan:
		case T_CteScan:
		case T_WorkTableScan:
		case T_SubqueryScan:
			show_scan_qual(plan->qual,
						   "Filter", plan, outer_plan, indent, 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", plan, outer_plan, indent, es);
				show_scan_qual(plan->qual,
							   "Filter", plan, outer_plan, indent, es);
			}
			break;
		case T_NestLoop:
			show_upper_qual(((NestLoop *) plan)->join.joinqual,
							"Join Filter", plan, indent, es);
			show_upper_qual(plan->qual, "Filter", plan, indent, es);
			break;
		case T_MergeJoin:
			show_upper_qual(((MergeJoin *) plan)->mergeclauses,
							"Merge Cond", plan, indent, es);
			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
							"Join Filter", plan, indent, es);
			show_upper_qual(plan->qual, "Filter", plan, indent, es);
			break;
		case T_HashJoin:
			show_upper_qual(((HashJoin *) plan)->hashclauses,
							"Hash Cond", plan, indent, es);
			show_upper_qual(((HashJoin *) plan)->join.joinqual,
							"Join Filter", plan, indent, es);
			show_upper_qual(plan->qual, "Filter", plan, indent, es);
			break;
		case T_Agg:
		case T_Group:
			show_upper_qual(plan->qual, "Filter", plan, indent, es);
			break;
		case T_Sort:
			show_sort_keys(plan, indent, es);
			show_sort_info((SortState *) planstate, indent, es);
			break;
		case T_Result:
			show_upper_qual((List *) ((Result *) plan)->resconstantqual,
							"One-Time Filter", plan, indent, es);
			show_upper_qual(plan->qual, "Filter", plan, indent, es);
			break;
		default:
			break;
	}

	/* initPlan-s */
	if (plan->initPlan)
		ExplainSubPlans(planstate->initPlan, indent, es);

	/* lefttree */
	if (outerPlan(plan))
	{
		/*
		 * Ordinarily we don't pass down our own outer_plan value to our child
		 * nodes, but in bitmap scan trees we must, since the bottom
		 * BitmapIndexScan nodes may have outer references.
		 */
		ExplainNode(outerPlan(plan), outerPlanState(planstate),
					IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
					indent + 3, es);
	}

	/* righttree */
	if (innerPlan(plan))
	{
		ExplainNode(innerPlan(plan), innerPlanState(planstate),
					outerPlan(plan), indent + 3, es);
	}

	/* special child plans */
	switch (nodeTag(plan))
	{
		case T_Append:
			ExplainMemberNodes(((Append *) plan)->appendplans,
							   ((AppendState *) planstate)->appendplans,
							   outer_plan, indent, es);
			break;
		case T_BitmapAnd:
			ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
							   ((BitmapAndState *) planstate)->bitmapplans,
							   outer_plan, indent, es);
			break;
		case T_BitmapOr:
			ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
							   ((BitmapOrState *) planstate)->bitmapplans,
							   outer_plan, indent, es);
			break;
		case T_SubqueryScan:
			{
				SubqueryScan *subqueryscan = (SubqueryScan *) plan;
				SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;

				ExplainNode(subqueryscan->subplan, subquerystate->subplan,
							NULL, indent + 3, es);
			}
			break;
		default:
			break;
	}

	/* subPlan-s */
	if (planstate->subPlan)
		ExplainSubPlans(planstate->subPlan, indent, es);
}

/*
 * Show the targetlist of a plan node
 */
static void
show_plan_tlist(Plan *plan, int indent, ExplainState *es)
{
	List	   *context;
	bool		useprefix;
	ListCell   *lc;
	int			i;

	/* 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 RecursiveUnion */
	if (IsA(plan, RecursiveUnion))
		return;

	/* Set up deparsing context */
	context = deparse_context_for_plan((Node *) plan,
									   NULL,
									   es->rtable,
									   es->pstmt->subplans);
	useprefix = list_length(es->rtable) > 1;

	/* Emit line prefix */
	appendStringInfoSpaces(es->str, indent * 2);
	appendStringInfoString(es->str, "  Output: ");

	/* Deparse each non-junk result column */
	i = 0;
	foreach(lc, plan->targetlist)
	{
		TargetEntry *tle = (TargetEntry *) lfirst(lc);

		if (tle->resjunk)
			continue;
		if (i++ > 0)
			appendStringInfoString(es->str, ", ");
		appendStringInfoString(es->str,
							   deparse_expression((Node *) tle->expr, context,
												  useprefix, false));
	}

	appendStringInfoChar(es->str, '\n');
}

/*
 * Show a qualifier expression
 *
 * Note: outer_plan is the referent for any OUTER vars in the scan qual;
 * this would be the outer side of a nestloop plan.  Pass NULL if none.
 */
static void
show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan,
		  int indent, bool useprefix, ExplainState *es)
{
	List	   *context;
	Node	   *node;
	char	   *exprstr;

	/* No work if empty qual */
	if (qual == NIL)
		return;

	/* Convert AND list to explicit AND */
	node = (Node *) make_ands_explicit(qual);

	/* Set up deparsing context */
	context = deparse_context_for_plan((Node *) plan,
									   (Node *) outer_plan,
									   es->rtable,
									   es->pstmt->subplans);

	/* Deparse the expression */
	exprstr = deparse_expression(node, context, useprefix, false);

	/* And add to es->str */
	appendStringInfoSpaces(es->str, indent * 2);
	appendStringInfo(es->str, "  %s: %s\n", qlabel, exprstr);
}

/*
 * Show a qualifier expression for a scan plan node
 */
static void
show_scan_qual(List *qual, const char *qlabel,
			   Plan *scan_plan, Plan *outer_plan,
			   int indent, ExplainState *es)
{
	bool		useprefix;

	useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan));
	show_qual(qual, qlabel, scan_plan, outer_plan, indent, useprefix, es);
}

/*
 * Show a qualifier expression for an upper-level plan node
 */
static void
show_upper_qual(List *qual, const char *qlabel, Plan *plan,
				int indent, ExplainState *es)
{
	bool		useprefix;

	useprefix = (list_length(es->rtable) > 1);
	show_qual(qual, qlabel, plan, NULL, indent, useprefix, es);
}

/*
 * Show the sort keys for a Sort node.
 */
static void
show_sort_keys(Plan *sortplan, int indent, ExplainState *es)
{
	int			nkeys = ((Sort *) sortplan)->numCols;
	AttrNumber *keycols = ((Sort *) sortplan)->sortColIdx;
	List	   *context;
	bool		useprefix;
	int			keyno;
	char	   *exprstr;

	if (nkeys <= 0)
		return;

	appendStringInfoSpaces(es->str, indent * 2);
	appendStringInfoString(es->str, "  Sort Key: ");

	/* Set up deparsing context */
	context = deparse_context_for_plan((Node *) sortplan,
									   NULL,
									   es->rtable,
									   es->pstmt->subplans);
	useprefix = list_length(es->rtable) > 1;

	for (keyno = 0; keyno < nkeys; keyno++)
	{
		/* find key expression in tlist */
		AttrNumber	keyresno = keycols[keyno];
		TargetEntry *target = get_tle_by_resno(sortplan->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);
		/* And add to es->str */
		if (keyno > 0)
			appendStringInfoString(es->str, ", ");
		appendStringInfoString(es->str, exprstr);
	}

	appendStringInfoChar(es->str, '\n');
}

/*
 * If it's EXPLAIN ANALYZE, show tuplesort explain info for a sort node
 */
static void
show_sort_info(SortState *sortstate, int indent, ExplainState *es)
{
	Assert(IsA(sortstate, SortState));
	if (es->analyze && sortstate->sort_Done &&
		sortstate->tuplesortstate != NULL)
	{
		char	   *sortinfo;

		sortinfo = tuplesort_explain((Tuplesortstate *) sortstate->tuplesortstate);
		appendStringInfoSpaces(es->str, indent * 2);
		appendStringInfo(es->str, "  %s\n", sortinfo);
		pfree(sortinfo);
	}
}

/*
 * 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)
{
	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;
}

/*
 * Show the target of a Scan node
 */
static void
ExplainScanTarget(Scan *plan, ExplainState *es)
{
	char	   *objectname = NULL;
	RangeTblEntry *rte;

	if (plan->scanrelid <= 0)	/* Is this still possible? */
		return;
	rte = rt_fetch(plan->scanrelid, es->rtable);

	switch (nodeTag(plan))
	{
		case T_SeqScan:
		case T_IndexScan:
		case T_BitmapHeapScan:
		case T_TidScan:
			/* Assert it's on a real relation */
			Assert(rte->rtekind == RTE_RELATION);
			objectname = get_rel_name(rte->relid);
			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);
				}
			}
			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;
			break;
		case T_WorkTableScan:
			/* Assert it's on a self-reference CTE */
			Assert(rte->rtekind == RTE_CTE);
			Assert(rte->self_reference);
			objectname = rte->ctename;
			break;
		default:
			break;
	}

	appendStringInfoString(es->str, " on");
	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));
}

/*
 * Explain the constituent plans of an Append, BitmapAnd, or BitmapOr node.
 *
 * Ordinarily we don't pass down outer_plan to our child nodes, but in these
 * cases we must, since the node could be an "inner indexscan" in which case
 * outer references can appear in the child nodes.
 */
static void
ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
		           int indent, ExplainState *es)
{
	ListCell   *lst;
	int			j = 0;

	foreach(lst, plans)
	{
		Plan	   *subnode = (Plan *) lfirst(lst);

		ExplainNode(subnode, planstate[j],
					outer_plan, indent + 3, es);
		j++;
	}
}

/*
 * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).
 */
static void
ExplainSubPlans(List *plans, int indent, ExplainState *es)
{
	ListCell   *lst;

	foreach(lst, plans)
	{
		SubPlanState *sps = (SubPlanState *) lfirst(lst);
		SubPlan    *sp = (SubPlan *) sps->xprstate.expr;

		appendStringInfoSpaces(es->str, indent * 2);
		appendStringInfo(es->str, "  %s\n", sp->plan_name);
		ExplainNode(exec_subplan_get_plan(es->pstmt, sp),
					sps->planstate, NULL, indent + 4, es);
	}
}
