mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-30 00:04:49 -04:00 
			
		
		
		
	The large majority of these have been introduced by recent commits done in the v18 development cycle. Author: Alexander Lakhin <exclusion@gmail.com> Discussion: https://postgr.es/m/9a7763ab-5252-429d-a943-b28941e0e28b@gmail.com
		
			
				
	
	
		
			775 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			775 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*-------------------------------------------------------------------------
 | |
|  *
 | |
|  * pg_overexplain.c
 | |
|  *	  allow EXPLAIN to dump even more details
 | |
|  *
 | |
|  * Copyright (c) 2016-2025, PostgreSQL Global Development Group
 | |
|  *
 | |
|  *	  contrib/pg_overexplain/pg_overexplain.c
 | |
|  *-------------------------------------------------------------------------
 | |
|  */
 | |
| #include "postgres.h"
 | |
| 
 | |
| #include "catalog/pg_class.h"
 | |
| #include "commands/defrem.h"
 | |
| #include "commands/explain.h"
 | |
| #include "commands/explain_format.h"
 | |
| #include "commands/explain_state.h"
 | |
| #include "fmgr.h"
 | |
| #include "parser/parsetree.h"
 | |
| #include "storage/lock.h"
 | |
| #include "utils/builtins.h"
 | |
| #include "utils/lsyscache.h"
 | |
| 
 | |
| PG_MODULE_MAGIC_EXT(
 | |
| 					.name = "pg_overexplain",
 | |
| 					.version = PG_VERSION
 | |
| );
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
| 	bool		debug;
 | |
| 	bool		range_table;
 | |
| } overexplain_options;
 | |
| 
 | |
| static overexplain_options *overexplain_ensure_options(ExplainState *es);
 | |
| static void overexplain_debug_handler(ExplainState *es, DefElem *opt,
 | |
| 									  ParseState *pstate);
 | |
| static void overexplain_range_table_handler(ExplainState *es, DefElem *opt,
 | |
| 											ParseState *pstate);
 | |
| static void overexplain_per_node_hook(PlanState *planstate, List *ancestors,
 | |
| 									  const char *relationship,
 | |
| 									  const char *plan_name,
 | |
| 									  ExplainState *es);
 | |
| static void overexplain_per_plan_hook(PlannedStmt *plannedstmt,
 | |
| 									  IntoClause *into,
 | |
| 									  ExplainState *es,
 | |
| 									  const char *queryString,
 | |
| 									  ParamListInfo params,
 | |
| 									  QueryEnvironment *queryEnv);
 | |
| static void overexplain_debug(PlannedStmt *plannedstmt, ExplainState *es);
 | |
| static void overexplain_range_table(PlannedStmt *plannedstmt,
 | |
| 									ExplainState *es);
 | |
| static void overexplain_alias(const char *qlabel, Alias *alias,
 | |
| 							  ExplainState *es);
 | |
| static void overexplain_bitmapset(const char *qlabel, Bitmapset *bms,
 | |
| 								  ExplainState *es);
 | |
| static void overexplain_intlist(const char *qlabel, List *list,
 | |
| 								ExplainState *es);
 | |
| 
 | |
| static int	es_extension_id;
 | |
| static explain_per_node_hook_type prev_explain_per_node_hook;
 | |
| static explain_per_plan_hook_type prev_explain_per_plan_hook;
 | |
| 
 | |
| /*
 | |
|  * Initialization we do when this module is loaded.
 | |
|  */
 | |
| void
 | |
| _PG_init(void)
 | |
| {
 | |
| 	/* Get an ID that we can use to cache data in an ExplainState. */
 | |
| 	es_extension_id = GetExplainExtensionId("pg_overexplain");
 | |
| 
 | |
| 	/* Register the new EXPLAIN options implemented by this module. */
 | |
| 	RegisterExtensionExplainOption("debug", overexplain_debug_handler);
 | |
| 	RegisterExtensionExplainOption("range_table",
 | |
| 								   overexplain_range_table_handler);
 | |
| 
 | |
| 	/* Use the per-node and per-plan hooks to make our options do something. */
 | |
| 	prev_explain_per_node_hook = explain_per_node_hook;
 | |
| 	explain_per_node_hook = overexplain_per_node_hook;
 | |
| 	prev_explain_per_plan_hook = explain_per_plan_hook;
 | |
| 	explain_per_plan_hook = overexplain_per_plan_hook;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Get the overexplain_options structure from an ExplainState; if there is
 | |
|  * none, create one, attach it to the ExplainState, and return it.
 | |
|  */
 | |
| static overexplain_options *
 | |
| overexplain_ensure_options(ExplainState *es)
 | |
| {
 | |
| 	overexplain_options *options;
 | |
| 
 | |
| 	options = GetExplainExtensionState(es, es_extension_id);
 | |
| 
 | |
| 	if (options == NULL)
 | |
| 	{
 | |
| 		options = palloc0(sizeof(overexplain_options));
 | |
| 		SetExplainExtensionState(es, es_extension_id, options);
 | |
| 	}
 | |
| 
 | |
| 	return options;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Parse handler for EXPLAIN (DEBUG).
 | |
|  */
 | |
| static void
 | |
| overexplain_debug_handler(ExplainState *es, DefElem *opt, ParseState *pstate)
 | |
| {
 | |
| 	overexplain_options *options = overexplain_ensure_options(es);
 | |
| 
 | |
| 	options->debug = defGetBoolean(opt);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Parse handler for EXPLAIN (RANGE_TABLE).
 | |
|  */
 | |
| static void
 | |
| overexplain_range_table_handler(ExplainState *es, DefElem *opt,
 | |
| 								ParseState *pstate)
 | |
| {
 | |
| 	overexplain_options *options = overexplain_ensure_options(es);
 | |
| 
 | |
| 	options->range_table = defGetBoolean(opt);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Print out additional per-node information as appropriate. If the user didn't
 | |
|  * specify any of the options we support, do nothing; else, print whatever is
 | |
|  * relevant to the specified options.
 | |
|  */
 | |
| static void
 | |
| overexplain_per_node_hook(PlanState *planstate, List *ancestors,
 | |
| 						  const char *relationship, const char *plan_name,
 | |
| 						  ExplainState *es)
 | |
| {
 | |
| 	overexplain_options *options;
 | |
| 	Plan	   *plan = planstate->plan;
 | |
| 
 | |
| 	if (prev_explain_per_node_hook)
 | |
| 		(*prev_explain_per_node_hook) (planstate, ancestors, relationship,
 | |
| 									   plan_name, es);
 | |
| 
 | |
| 	options = GetExplainExtensionState(es, es_extension_id);
 | |
| 	if (options == NULL)
 | |
| 		return;
 | |
| 
 | |
| 	/*
 | |
| 	 * If the "debug" option was given, display miscellaneous fields from the
 | |
| 	 * "Plan" node that would not otherwise be displayed.
 | |
| 	 */
 | |
| 	if (options->debug)
 | |
| 	{
 | |
| 		/*
 | |
| 		 * Normal EXPLAIN will display "Disabled: true" if the node is
 | |
| 		 * disabled; but that is based on noticing that plan->disabled_nodes
 | |
| 		 * is higher than the sum of its children; here, we display the raw
 | |
| 		 * value, for debugging purposes.
 | |
| 		 */
 | |
| 		ExplainPropertyInteger("Disabled Nodes", NULL, plan->disabled_nodes,
 | |
| 							   es);
 | |
| 
 | |
| 		/*
 | |
| 		 * Normal EXPLAIN will display the parallel_aware flag; here, we show
 | |
| 		 * the parallel_safe flag as well.
 | |
| 		 */
 | |
| 		ExplainPropertyBool("Parallel Safe", plan->parallel_safe, es);
 | |
| 
 | |
| 		/*
 | |
| 		 * The plan node ID isn't normally displayed, since it is only useful
 | |
| 		 * for debugging.
 | |
| 		 */
 | |
| 		ExplainPropertyInteger("Plan Node ID", NULL, plan->plan_node_id, es);
 | |
| 
 | |
| 		/*
 | |
| 		 * It is difficult to explain what extParam and allParam mean in plain
 | |
| 		 * language, so we simply display these fields labelled with the
 | |
| 		 * structure member name. For compactness, the text format omits the
 | |
| 		 * display of this information when the bitmapset is empty.
 | |
| 		 */
 | |
| 		if (es->format != EXPLAIN_FORMAT_TEXT || !bms_is_empty(plan->extParam))
 | |
| 			overexplain_bitmapset("extParam", plan->extParam, es);
 | |
| 		if (es->format != EXPLAIN_FORMAT_TEXT || !bms_is_empty(plan->allParam))
 | |
| 			overexplain_bitmapset("allParam", plan->allParam, es);
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * If the "range_table" option was specified, display information about
 | |
| 	 * the range table indexes for this node.
 | |
| 	 */
 | |
| 	if (options->range_table)
 | |
| 	{
 | |
| 		switch (nodeTag(plan))
 | |
| 		{
 | |
| 			case T_SeqScan:
 | |
| 			case T_SampleScan:
 | |
| 			case T_IndexScan:
 | |
| 			case T_IndexOnlyScan:
 | |
| 			case T_BitmapHeapScan:
 | |
| 			case T_TidScan:
 | |
| 			case T_TidRangeScan:
 | |
| 			case T_SubqueryScan:
 | |
| 			case T_FunctionScan:
 | |
| 			case T_TableFuncScan:
 | |
| 			case T_ValuesScan:
 | |
| 			case T_CteScan:
 | |
| 			case T_NamedTuplestoreScan:
 | |
| 			case T_WorkTableScan:
 | |
| 				ExplainPropertyInteger("Scan RTI", NULL,
 | |
| 									   ((Scan *) plan)->scanrelid, es);
 | |
| 				break;
 | |
| 			case T_ForeignScan:
 | |
| 				overexplain_bitmapset("Scan RTIs",
 | |
| 									  ((ForeignScan *) plan)->fs_base_relids,
 | |
| 									  es);
 | |
| 				break;
 | |
| 			case T_CustomScan:
 | |
| 				overexplain_bitmapset("Scan RTIs",
 | |
| 									  ((CustomScan *) plan)->custom_relids,
 | |
| 									  es);
 | |
| 				break;
 | |
| 			case T_ModifyTable:
 | |
| 				ExplainPropertyInteger("Nominal RTI", NULL,
 | |
| 									   ((ModifyTable *) plan)->nominalRelation, es);
 | |
| 				ExplainPropertyInteger("Exclude Relation RTI", NULL,
 | |
| 									   ((ModifyTable *) plan)->exclRelRTI, es);
 | |
| 				break;
 | |
| 			case T_Append:
 | |
| 				overexplain_bitmapset("Append RTIs",
 | |
| 									  ((Append *) plan)->apprelids,
 | |
| 									  es);
 | |
| 				break;
 | |
| 			case T_MergeAppend:
 | |
| 				overexplain_bitmapset("Append RTIs",
 | |
| 									  ((MergeAppend *) plan)->apprelids,
 | |
| 									  es);
 | |
| 				break;
 | |
| 			default:
 | |
| 				break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Print out additional per-query information as appropriate. Here again, if
 | |
|  * the user didn't specify any of the options implemented by this module, do
 | |
|  * nothing; otherwise, call the appropriate function for each specified
 | |
|  * option.
 | |
|  */
 | |
| static void
 | |
| overexplain_per_plan_hook(PlannedStmt *plannedstmt,
 | |
| 						  IntoClause *into,
 | |
| 						  ExplainState *es,
 | |
| 						  const char *queryString,
 | |
| 						  ParamListInfo params,
 | |
| 						  QueryEnvironment *queryEnv)
 | |
| {
 | |
| 	overexplain_options *options;
 | |
| 
 | |
| 	if (prev_explain_per_plan_hook)
 | |
| 		(*prev_explain_per_plan_hook) (plannedstmt, into, es, queryString,
 | |
| 									   params, queryEnv);
 | |
| 
 | |
| 	options = GetExplainExtensionState(es, es_extension_id);
 | |
| 	if (options == NULL)
 | |
| 		return;
 | |
| 
 | |
| 	if (options->debug)
 | |
| 		overexplain_debug(plannedstmt, es);
 | |
| 
 | |
| 	if (options->range_table)
 | |
| 		overexplain_range_table(plannedstmt, es);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Print out various details from the PlannedStmt that wouldn't otherwise
 | |
|  * be displayed.
 | |
|  *
 | |
|  * We don't try to print everything here. Information that would be displayed
 | |
|  * anyway doesn't need to be printed again here, and things with lots of
 | |
|  * substructure probably should be printed via separate options, or not at all.
 | |
|  */
 | |
| static void
 | |
| overexplain_debug(PlannedStmt *plannedstmt, ExplainState *es)
 | |
| {
 | |
| 	char	   *commandType = NULL;
 | |
| 	StringInfoData flags;
 | |
| 
 | |
| 	/* Even in text mode, we want to set this output apart as its own group. */
 | |
| 	ExplainOpenGroup("PlannedStmt", "PlannedStmt", true, es);
 | |
| 	if (es->format == EXPLAIN_FORMAT_TEXT)
 | |
| 	{
 | |
| 		ExplainIndentText(es);
 | |
| 		appendStringInfoString(es->str, "PlannedStmt:\n");
 | |
| 		es->indent++;
 | |
| 	}
 | |
| 
 | |
| 	/* Print the command type. */
 | |
| 	switch (plannedstmt->commandType)
 | |
| 	{
 | |
| 		case CMD_UNKNOWN:
 | |
| 			commandType = "unknown";
 | |
| 			break;
 | |
| 		case CMD_SELECT:
 | |
| 			commandType = "select";
 | |
| 			break;
 | |
| 		case CMD_UPDATE:
 | |
| 			commandType = "update";
 | |
| 			break;
 | |
| 		case CMD_INSERT:
 | |
| 			commandType = "insert";
 | |
| 			break;
 | |
| 		case CMD_DELETE:
 | |
| 			commandType = "delete";
 | |
| 			break;
 | |
| 		case CMD_MERGE:
 | |
| 			commandType = "merge";
 | |
| 			break;
 | |
| 		case CMD_UTILITY:
 | |
| 			commandType = "utility";
 | |
| 			break;
 | |
| 		case CMD_NOTHING:
 | |
| 			commandType = "nothing";
 | |
| 			break;
 | |
| 	}
 | |
| 	ExplainPropertyText("Command Type", commandType, es);
 | |
| 
 | |
| 	/* Print various properties as a comma-separated list of flags. */
 | |
| 	initStringInfo(&flags);
 | |
| 	if (plannedstmt->hasReturning)
 | |
| 		appendStringInfoString(&flags, ", hasReturning");
 | |
| 	if (plannedstmt->hasModifyingCTE)
 | |
| 		appendStringInfoString(&flags, ", hasModifyingCTE");
 | |
| 	if (plannedstmt->canSetTag)
 | |
| 		appendStringInfoString(&flags, ", canSetTag");
 | |
| 	if (plannedstmt->transientPlan)
 | |
| 		appendStringInfoString(&flags, ", transientPlan");
 | |
| 	if (plannedstmt->dependsOnRole)
 | |
| 		appendStringInfoString(&flags, ", dependsOnRole");
 | |
| 	if (plannedstmt->parallelModeNeeded)
 | |
| 		appendStringInfoString(&flags, ", parallelModeNeeded");
 | |
| 	if (flags.len == 0)
 | |
| 		appendStringInfoString(&flags, ", none");
 | |
| 	ExplainPropertyText("Flags", flags.data + 2, es);
 | |
| 
 | |
| 	/* Various lists of integers. */
 | |
| 	overexplain_bitmapset("Subplans Needing Rewind",
 | |
| 						  plannedstmt->rewindPlanIDs, es);
 | |
| 	overexplain_intlist("Relation OIDs",
 | |
| 						plannedstmt->relationOids, es);
 | |
| 	overexplain_intlist("Executor Parameter Types",
 | |
| 						plannedstmt->paramExecTypes, es);
 | |
| 
 | |
| 	/*
 | |
| 	 * Print the statement location. (If desired, we could alternatively print
 | |
| 	 * stmt_location and stmt_len as two separate fields.)
 | |
| 	 */
 | |
| 	if (plannedstmt->stmt_location == -1)
 | |
| 		ExplainPropertyText("Parse Location", "Unknown", es);
 | |
| 	else if (plannedstmt->stmt_len == 0)
 | |
| 		ExplainPropertyText("Parse Location",
 | |
| 							psprintf("%d to end", plannedstmt->stmt_location),
 | |
| 							es);
 | |
| 	else
 | |
| 		ExplainPropertyText("Parse Location",
 | |
| 							psprintf("%d for %d bytes",
 | |
| 									 plannedstmt->stmt_location,
 | |
| 									 plannedstmt->stmt_len),
 | |
| 							es);
 | |
| 
 | |
| 	/* Done with this group. */
 | |
| 	if (es->format == EXPLAIN_FORMAT_TEXT)
 | |
| 		es->indent--;
 | |
| 	ExplainCloseGroup("PlannedStmt", "PlannedStmt", true, es);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Provide detailed information about the contents of the PlannedStmt's
 | |
|  * range table.
 | |
|  */
 | |
| static void
 | |
| overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
 | |
| {
 | |
| 	Index		rti;
 | |
| 
 | |
| 	/* Open group, one entry per RangeTblEntry */
 | |
| 	ExplainOpenGroup("Range Table", "Range Table", false, es);
 | |
| 
 | |
| 	/* Iterate over the range table */
 | |
| 	for (rti = 1; rti <= list_length(plannedstmt->rtable); ++rti)
 | |
| 	{
 | |
| 		RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable);
 | |
| 		char	   *kind = NULL;
 | |
| 		char	   *relkind;
 | |
| 
 | |
| 		/* NULL entries are possible; skip them */
 | |
| 		if (rte == NULL)
 | |
| 			continue;
 | |
| 
 | |
| 		/* Translate rtekind to a string */
 | |
| 		switch (rte->rtekind)
 | |
| 		{
 | |
| 			case RTE_RELATION:
 | |
| 				kind = "relation";
 | |
| 				break;
 | |
| 			case RTE_SUBQUERY:
 | |
| 				kind = "subquery";
 | |
| 				break;
 | |
| 			case RTE_JOIN:
 | |
| 				kind = "join";
 | |
| 				break;
 | |
| 			case RTE_FUNCTION:
 | |
| 				kind = "function";
 | |
| 				break;
 | |
| 			case RTE_TABLEFUNC:
 | |
| 				kind = "tablefunc";
 | |
| 				break;
 | |
| 			case RTE_VALUES:
 | |
| 				kind = "values";
 | |
| 				break;
 | |
| 			case RTE_CTE:
 | |
| 				kind = "cte";
 | |
| 				break;
 | |
| 			case RTE_NAMEDTUPLESTORE:
 | |
| 				kind = "namedtuplestore";
 | |
| 				break;
 | |
| 			case RTE_RESULT:
 | |
| 				kind = "result";
 | |
| 				break;
 | |
| 			case RTE_GROUP:
 | |
| 				kind = "group";
 | |
| 				break;
 | |
| 		}
 | |
| 
 | |
| 		/* Begin group for this specific RTE */
 | |
| 		ExplainOpenGroup("Range Table Entry", NULL, true, es);
 | |
| 
 | |
| 		/*
 | |
| 		 * In text format, the summary line displays the range table index and
 | |
| 		 * rtekind, plus indications if rte->inh and/or rte->inFromCl are set.
 | |
| 		 * In other formats, we display those as separate properties.
 | |
| 		 */
 | |
| 		if (es->format == EXPLAIN_FORMAT_TEXT)
 | |
| 		{
 | |
| 			ExplainIndentText(es);
 | |
| 			appendStringInfo(es->str, "RTI %u (%s%s%s):\n", rti, kind,
 | |
| 							 rte->inh ? ", inherited" : "",
 | |
| 							 rte->inFromCl ? ", in-from-clause" : "");
 | |
| 			es->indent++;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			ExplainPropertyUInteger("RTI", NULL, rti, es);
 | |
| 			ExplainPropertyText("Kind", kind, es);
 | |
| 			ExplainPropertyBool("Inherited", rte->inh, es);
 | |
| 			ExplainPropertyBool("In From Clause", rte->inFromCl, es);
 | |
| 		}
 | |
| 
 | |
| 		/* rte->alias is optional; rte->eref is requested */
 | |
| 		if (rte->alias != NULL)
 | |
| 			overexplain_alias("Alias", rte->alias, es);
 | |
| 		overexplain_alias("Eref", rte->eref, es);
 | |
| 
 | |
| 		/*
 | |
| 		 * We adhere to the usual EXPLAIN convention that schema names are
 | |
| 		 * displayed only in verbose mode, and we emit nothing if there is no
 | |
| 		 * relation OID.
 | |
| 		 */
 | |
| 		if (rte->relid != 0)
 | |
| 		{
 | |
| 			const char *relname;
 | |
| 			const char *qualname;
 | |
| 
 | |
| 			relname = quote_identifier(get_rel_name(rte->relid));
 | |
| 
 | |
| 			if (es->verbose)
 | |
| 			{
 | |
| 				Oid			nspoid = get_rel_namespace(rte->relid);
 | |
| 				char	   *nspname;
 | |
| 
 | |
| 				nspname = get_namespace_name_or_temp(nspoid);
 | |
| 				qualname = psprintf("%s.%s", quote_identifier(nspname),
 | |
| 									relname);
 | |
| 			}
 | |
| 			else
 | |
| 				qualname = relname;
 | |
| 
 | |
| 			ExplainPropertyText("Relation", qualname, es);
 | |
| 		}
 | |
| 
 | |
| 		/* Translate relkind, if any, to a string */
 | |
| 		switch (rte->relkind)
 | |
| 		{
 | |
| 			case RELKIND_RELATION:
 | |
| 				relkind = "relation";
 | |
| 				break;
 | |
| 			case RELKIND_INDEX:
 | |
| 				relkind = "index";
 | |
| 				break;
 | |
| 			case RELKIND_SEQUENCE:
 | |
| 				relkind = "sequence";
 | |
| 				break;
 | |
| 			case RELKIND_TOASTVALUE:
 | |
| 				relkind = "toastvalue";
 | |
| 				break;
 | |
| 			case RELKIND_VIEW:
 | |
| 				relkind = "view";
 | |
| 				break;
 | |
| 			case RELKIND_MATVIEW:
 | |
| 				relkind = "matview";
 | |
| 				break;
 | |
| 			case RELKIND_COMPOSITE_TYPE:
 | |
| 				relkind = "composite_type";
 | |
| 				break;
 | |
| 			case RELKIND_FOREIGN_TABLE:
 | |
| 				relkind = "foreign_table";
 | |
| 				break;
 | |
| 			case RELKIND_PARTITIONED_TABLE:
 | |
| 				relkind = "partitioned_table";
 | |
| 				break;
 | |
| 			case RELKIND_PARTITIONED_INDEX:
 | |
| 				relkind = "partitioned_index";
 | |
| 				break;
 | |
| 			case '\0':
 | |
| 				relkind = NULL;
 | |
| 				break;
 | |
| 			default:
 | |
| 				relkind = psprintf("%c", rte->relkind);
 | |
| 				break;
 | |
| 		}
 | |
| 
 | |
| 		/* If there is a relkind, show it */
 | |
| 		if (relkind != NULL)
 | |
| 			ExplainPropertyText("Relation Kind", relkind, es);
 | |
| 
 | |
| 		/* If there is a lock mode, show it */
 | |
| 		if (rte->rellockmode != 0)
 | |
| 			ExplainPropertyText("Relation Lock Mode",
 | |
| 								GetLockmodeName(DEFAULT_LOCKMETHOD,
 | |
| 												rte->rellockmode), es);
 | |
| 
 | |
| 		/*
 | |
| 		 * If there is a perminfoindex, show it. We don't try to display
 | |
| 		 * information from the RTEPermissionInfo node here because they are
 | |
| 		 * just indexes plannedstmt->permInfos which could be separately
 | |
| 		 * dumped if someone wants to add EXPLAIN (PERMISSIONS) or similar.
 | |
| 		 */
 | |
| 		if (rte->perminfoindex != 0)
 | |
| 			ExplainPropertyInteger("Permission Info Index", NULL,
 | |
| 								   rte->perminfoindex, es);
 | |
| 
 | |
| 		/*
 | |
| 		 * add_rte_to_flat_rtable will clear rte->tablesample and
 | |
| 		 * rte->subquery in the finished plan, so skip those fields.
 | |
| 		 *
 | |
| 		 * However, the security_barrier flag is not shown by the core code,
 | |
| 		 * so let's print it here.
 | |
| 		 */
 | |
| 		if (es->format != EXPLAIN_FORMAT_TEXT || rte->security_barrier)
 | |
| 			ExplainPropertyBool("Security Barrier", rte->security_barrier, es);
 | |
| 
 | |
| 		/*
 | |
| 		 * If this is a join, print out the fields that are specifically valid
 | |
| 		 * for joins.
 | |
| 		 */
 | |
| 		if (rte->rtekind == RTE_JOIN)
 | |
| 		{
 | |
| 			char	   *jointype;
 | |
| 
 | |
| 			switch (rte->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;
 | |
| 				case JOIN_RIGHT_SEMI:
 | |
| 					jointype = "Right Semi";
 | |
| 					break;
 | |
| 				case JOIN_RIGHT_ANTI:
 | |
| 					jointype = "Right Anti";
 | |
| 					break;
 | |
| 				default:
 | |
| 					jointype = "???";
 | |
| 					break;
 | |
| 			}
 | |
| 
 | |
| 			/* Join type */
 | |
| 			ExplainPropertyText("Join Type", jointype, es);
 | |
| 
 | |
| 			/* # of JOIN USING columns */
 | |
| 			if (es->format != EXPLAIN_FORMAT_TEXT || rte->joinmergedcols != 0)
 | |
| 				ExplainPropertyInteger("JOIN USING Columns", NULL,
 | |
| 									   rte->joinmergedcols, es);
 | |
| 
 | |
| 			/*
 | |
| 			 * add_rte_to_flat_rtable will clear joinaliasvars, joinleftcols,
 | |
| 			 * joinrightcols, and join_using_alias here, so skip those fields.
 | |
| 			 */
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * add_rte_to_flat_rtable will clear functions, tablefunc, and
 | |
| 		 * values_lists, but we can display funcordinality.
 | |
| 		 */
 | |
| 		if (rte->rtekind == RTE_FUNCTION)
 | |
| 			ExplainPropertyBool("WITH ORDINALITY", rte->funcordinality, es);
 | |
| 
 | |
| 		/*
 | |
| 		 * If this is a CTE, print out CTE-related properties.
 | |
| 		 */
 | |
| 		if (rte->rtekind == RTE_CTE)
 | |
| 		{
 | |
| 			ExplainPropertyText("CTE Name", rte->ctename, es);
 | |
| 			ExplainPropertyUInteger("CTE Levels Up", NULL, rte->ctelevelsup,
 | |
| 									es);
 | |
| 			ExplainPropertyBool("CTE Self-Reference", rte->self_reference, es);
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * add_rte_to_flat_rtable will clear coltypes, coltypmods, and
 | |
| 		 * colcollations, so skip those fields.
 | |
| 		 *
 | |
| 		 * If this is an ephemeral named relation, print out ENR-related
 | |
| 		 * properties.
 | |
| 		 */
 | |
| 		if (rte->rtekind == RTE_NAMEDTUPLESTORE)
 | |
| 		{
 | |
| 			ExplainPropertyText("ENR Name", rte->enrname, es);
 | |
| 			ExplainPropertyFloat("ENR Tuples", NULL, rte->enrtuples, 0, es);
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * add_rte_to_flat_rtable will clear groupexprs and securityQuals, so
 | |
| 		 * skip that field. We have handled inFromCl above, so the only thing
 | |
| 		 * left to handle here is rte->lateral.
 | |
| 		 */
 | |
| 		if (es->format != EXPLAIN_FORMAT_TEXT || rte->lateral)
 | |
| 			ExplainPropertyBool("Lateral", rte->lateral, es);
 | |
| 
 | |
| 		/* Done with this RTE */
 | |
| 		if (es->format == EXPLAIN_FORMAT_TEXT)
 | |
| 			es->indent--;
 | |
| 		ExplainCloseGroup("Range Table Entry", NULL, true, es);
 | |
| 	}
 | |
| 
 | |
| 	/* Print PlannedStmt fields that contain RTIs. */
 | |
| 	if (es->format != EXPLAIN_FORMAT_TEXT ||
 | |
| 		!bms_is_empty(plannedstmt->unprunableRelids))
 | |
| 		overexplain_bitmapset("Unprunable RTIs", plannedstmt->unprunableRelids,
 | |
| 							  es);
 | |
| 	if (es->format != EXPLAIN_FORMAT_TEXT ||
 | |
| 		plannedstmt->resultRelations != NIL)
 | |
| 		overexplain_intlist("Result RTIs", plannedstmt->resultRelations, es);
 | |
| 
 | |
| 	/* Close group, we're all done */
 | |
| 	ExplainCloseGroup("Range Table", "Range Table", false, es);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Emit a text property describing the contents of an Alias.
 | |
|  *
 | |
|  * Column lists can be quite long here, so perhaps we should have an option
 | |
|  * to limit the display length by # of column or # of characters, but for
 | |
|  * now, just display everything.
 | |
|  */
 | |
| static void
 | |
| overexplain_alias(const char *qlabel, Alias *alias, ExplainState *es)
 | |
| {
 | |
| 	StringInfoData buf;
 | |
| 	bool		first = true;
 | |
| 
 | |
| 	Assert(alias != NULL);
 | |
| 
 | |
| 	initStringInfo(&buf);
 | |
| 	appendStringInfo(&buf, "%s (", quote_identifier(alias->aliasname));
 | |
| 
 | |
| 	foreach_node(String, cn, alias->colnames)
 | |
| 	{
 | |
| 		appendStringInfo(&buf, "%s%s",
 | |
| 						 first ? "" : ", ",
 | |
| 						 quote_identifier(cn->sval));
 | |
| 		first = false;
 | |
| 	}
 | |
| 
 | |
| 	appendStringInfoChar(&buf, ')');
 | |
| 	ExplainPropertyText(qlabel, buf.data, es);
 | |
| 	pfree(buf.data);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Emit a text property describing the contents of a bitmapset -- either a
 | |
|  * space-separated list of integer members, or the word "none" if the bitmapset
 | |
|  * is empty.
 | |
|  */
 | |
| static void
 | |
| overexplain_bitmapset(const char *qlabel, Bitmapset *bms, ExplainState *es)
 | |
| {
 | |
| 	int			x = -1;
 | |
| 
 | |
| 	StringInfoData buf;
 | |
| 
 | |
| 	if (bms_is_empty(bms))
 | |
| 	{
 | |
| 		ExplainPropertyText(qlabel, "none", es);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	initStringInfo(&buf);
 | |
| 	while ((x = bms_next_member(bms, x)) >= 0)
 | |
| 		appendStringInfo(&buf, " %d", x);
 | |
| 	Assert(buf.data[0] == ' ');
 | |
| 	ExplainPropertyText(qlabel, buf.data + 1, es);
 | |
| 	pfree(buf.data);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Emit a text property describing the contents of a list of integers, OIDs,
 | |
|  * or XIDs -- either a space-separated list of integer members, or the word
 | |
|  * "none" if the list is empty.
 | |
|  */
 | |
| static void
 | |
| overexplain_intlist(const char *qlabel, List *list, ExplainState *es)
 | |
| {
 | |
| 	StringInfoData buf;
 | |
| 
 | |
| 	initStringInfo(&buf);
 | |
| 
 | |
| 	if (list == NIL)
 | |
| 	{
 | |
| 		ExplainPropertyText(qlabel, "none", es);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (IsA(list, IntList))
 | |
| 	{
 | |
| 		foreach_int(i, list)
 | |
| 			appendStringInfo(&buf, " %d", i);
 | |
| 	}
 | |
| 	else if (IsA(list, OidList))
 | |
| 	{
 | |
| 		foreach_oid(o, list)
 | |
| 			appendStringInfo(&buf, " %u", o);
 | |
| 	}
 | |
| 	else if (IsA(list, XidList))
 | |
| 	{
 | |
| 		foreach_xid(x, list)
 | |
| 			appendStringInfo(&buf, " %u", x);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		appendStringInfoString(&buf, " not an integer list");
 | |
| 		Assert(false);
 | |
| 	}
 | |
| 
 | |
| 	if (buf.len > 0)
 | |
| 		ExplainPropertyText(qlabel, buf.data + 1, es);
 | |
| 
 | |
| 	pfree(buf.data);
 | |
| }
 |