mirror of
https://github.com/postgres/postgres.git
synced 2025-04-18 13:44:19 +03:00
Make sure that function declarations use names that exactly match the corresponding names from function definitions in a few places. These inconsistencies were all introduced during Postgres 18 development. This commit was written with help from clang-tidy, by mechanically applying the same rules as similar clean-up commits (the earliest such commit was commit 035ce1fe).
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 displyed
|
|
* 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 = "parititioned_table";
|
|
break;
|
|
case RELKIND_PARTITIONED_INDEX:
|
|
relkind = "parititioned_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, coltypemods, 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 columsn 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);
|
|
}
|