Thread

  1. [PATCH 1/3] Add a new option STATS to EXPLAIN command

    Tatsuro Yamada <yamatattsu@gmail.com> — 2025-02-09T23:33:23Z

    This patch allows to show applied extended statistics in EXPLAIN command output.
    It includes the following points:
    
    - Rebased on 9926f854
    
    - Added a new struct Applied_ExtStats in plannode.h (T4)
        - Hopefully this will solve the issue related to "-DCOPY_PARSE_PLAN_TREES"
          with cfbot.
        - To pass the extended statistics information from path to plan,
          it might be more appropriate to define a new structure in primnode.h
          rather than plannode.h. Any advice would be appreciated.
    
    - Handled EXPLAIN(STATS, VERBOSE) option (M2)
        - Before the fix, schema name was always added to extended statistics name,
          but with this patch, schema name is added to the following only when
          VERBOSE option is selected:
            - Extended statistics name, table name, and column name
    
    - Added Supported extended statistics types in document (M4)
    ---
     doc/src/sgml/ref/explain.sgml             |  14 +++
     src/backend/commands/explain.c            | 138 ++++++++++++++++++++++
     src/backend/commands/explain_state.c      |   2 +
     src/backend/nodes/makefuncs.c             |  11 ++
     src/backend/optimizer/plan/createplan.c   |  17 +++
     src/backend/optimizer/util/relnode.c      |  12 ++
     src/backend/optimizer/util/restrictinfo.c |  35 ++++++
     src/backend/statistics/extended_stats.c   |   8 ++
     src/backend/utils/adt/selfuncs.c          |  15 +++
     src/backend/utils/cache/lsyscache.c       |  49 ++++++++
     src/include/commands/explain_state.h      |   1 +
     src/include/nodes/makefuncs.h             |   2 +
     src/include/nodes/parsenodes.h            |   3 +
     src/include/nodes/pathnodes.h             |   5 +
     src/include/nodes/plannodes.h             |  15 +++
     src/include/optimizer/restrictinfo.h      |   2 +
     src/include/utils/lsyscache.h             |   3 +
     17 files changed, 332 insertions(+)
    
    diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
    index 6dda680aa0d..6bfe694dae8 100644
    --- a/doc/src/sgml/ref/explain.sgml
    +++ b/doc/src/sgml/ref/explain.sgml
    @@ -43,6 +43,7 @@ EXPLAIN [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] <rep
         BUFFERS [ <replaceable class="parameter">boolean</replaceable> ]
         SERIALIZE [ { NONE | TEXT | BINARY } ]
         WAL [ <replaceable class="parameter">boolean</replaceable> ]
    +    STATS [ <replaceable class="parameter">boolean</replaceable> ]
         TIMING [ <replaceable class="parameter">boolean</replaceable> ]
         SUMMARY [ <replaceable class="parameter">boolean</replaceable> ]
         MEMORY [ <replaceable class="parameter">boolean</replaceable> ]
    @@ -249,6 +250,19 @@ ROLLBACK;
         </listitem>
        </varlistentry>
     
    +   <varlistentry>
    +    <term><literal>STATS</literal></term>
    +    <listitem>
    +     <para>
    +      Include information on applied <literal>Extended statistics</literal>.
    +      Specifically, include the names of extended statistics and clauses.
    +      Supported extended statistics types are Dependencies and MCV.
    +      See <xref linkend="planner-stats-extended"/> for details about extended
    +      statistics. This parameter defaults to <literal>FALSE</literal>.
    +     </para>
    +    </listitem>
    +   </varlistentry>
    +
        <varlistentry>
         <term><literal>TIMING</literal></term>
         <listitem>
    diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
    index 8345bc0264b..53593775939 100644
    --- a/src/backend/commands/explain.c
    +++ b/src/backend/commands/explain.c
    @@ -87,6 +87,15 @@ static void show_qual(List *qual, const char *qlabel,
     static void show_scan_qual(List *qual, const char *qlabel,
     						   PlanState *planstate, List *ancestors,
     						   ExplainState *es);
    +static char *deparse_stat_expression(Node *node,
    +									 PlanState *planstate, List *ancestors,
    +									 bool useprefix, ExplainState *es);
    +static char *show_stat_qual(List *qual, int is_or,
    +							PlanState *planstate, List *ancestors,
    +							bool useprefix, ExplainState *es);
    +static void show_scan_stats(List *stats, List *clauses, List *ors,
    +							PlanState *planstate, List *ancestors,
    +							ExplainState *es);
     static void show_upper_qual(List *qual, const char *qlabel,
     							PlanState *planstate, List *ancestors,
     							ExplainState *es);
    @@ -347,6 +356,11 @@ standard_ExplainOneQuery(Query *query, int cursorOptions,
     
     	if (es->buffers)
     		bufusage_start = pgBufferUsage;
    +
    +	/* if this flag is true, applied ext stats are stored */
    +	if (es->stats)
    +		query->isExplain_Stats = true;
    +
     	INSTR_TIME_SET_CURRENT(planstart);
     
     	/* plan the query */
    @@ -1966,6 +1980,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
     				show_instrumentation_count("Rows Removed by Filter", 1,
     										   planstate, es);
     			show_indexsearches_info(planstate, es);
    +			if (es->stats)
    +				show_scan_stats(plan->app_extstats->applied_stats,
    +								plan->app_extstats->applied_clauses,
    +								plan->app_extstats->applied_clauses_or,
    +								planstate, ancestors, es);
     			break;
     		case T_IndexOnlyScan:
     			show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
    @@ -1983,11 +2002,21 @@ ExplainNode(PlanState *planstate, List *ancestors,
     				ExplainPropertyFloat("Heap Fetches", NULL,
     									 planstate->instrument->ntuples2, 0, es);
     			show_indexsearches_info(planstate, es);
    +			if (es->stats)
    +				show_scan_stats(plan->app_extstats->applied_stats,
    +								plan->app_extstats->applied_clauses,
    +								plan->app_extstats->applied_clauses_or,
    +								planstate, ancestors, es);
     			break;
     		case T_BitmapIndexScan:
     			show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
     						   "Index Cond", planstate, ancestors, es);
     			show_indexsearches_info(planstate, es);
    +			if (es->stats)
    +				show_scan_stats(plan->app_extstats->applied_stats,
    +								plan->app_extstats->applied_clauses,
    +								plan->app_extstats->applied_clauses_or,
    +								planstate, ancestors, es);
     			break;
     		case T_BitmapHeapScan:
     			show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
    @@ -2018,6 +2047,12 @@ ExplainNode(PlanState *planstate, List *ancestors,
     										   planstate, es);
     			if (IsA(plan, CteScan))
     				show_ctescan_info(castNode(CteScanState, planstate), es);
    +
    +			if (es->stats)
    +				show_scan_stats(plan->app_extstats->applied_stats,
    +								plan->app_extstats->applied_clauses,
    +								plan->app_extstats->applied_clauses_or,
    +								planstate, ancestors, es);
     			break;
     		case T_Gather:
     			{
    @@ -2085,6 +2120,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
     			if (plan->qual)
     				show_instrumentation_count("Rows Removed by Filter", 1,
     										   planstate, es);
    +			if (es->stats)
    +				show_scan_stats(plan->app_extstats->applied_stats,
    +								plan->app_extstats->applied_clauses,
    +								plan->app_extstats->applied_clauses_or,
    +								planstate, ancestors, es);
     			break;
     		case T_TableFuncScan:
     			if (es->verbose)
    @@ -2199,6 +2239,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
     			if (plan->qual)
     				show_instrumentation_count("Rows Removed by Filter", 1,
     										   planstate, es);
    +			if (es->stats)
    +				show_scan_stats(plan->app_extstats->applied_stats,
    +								plan->app_extstats->applied_clauses,
    +								plan->app_extstats->applied_clauses_or,
    +								planstate, ancestors, es);
     			break;
     		case T_WindowAgg:
     			show_window_def(castNode(WindowAggState, planstate), ancestors, es);
    @@ -2216,6 +2261,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
     			if (plan->qual)
     				show_instrumentation_count("Rows Removed by Filter", 1,
     										   planstate, es);
    +			if (es->stats)
    +				show_scan_stats(plan->app_extstats->applied_stats,
    +								plan->app_extstats->applied_clauses,
    +								plan->app_extstats->applied_clauses_or,
    +								planstate, ancestors, es);
     			break;
     		case T_Sort:
     			show_sort_keys(castNode(SortState, planstate), ancestors, es);
    @@ -2547,6 +2597,94 @@ show_scan_qual(List *qual, const char *qlabel,
     	show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
     }
     
    +/*
    + * Show a generic expression
    + */
    +static char *
    +deparse_stat_expression(Node *node,
    +						PlanState *planstate, List *ancestors,
    +						bool useprefix, ExplainState *es)
    +{
    +	List	   *context;
    +
    +	/* Set up deparsing context */
    +	context = set_deparse_context_plan(es->deparse_cxt,
    +									   planstate->plan,
    +									   ancestors);
    +
    +	/* Deparse the expression */
    +	return deparse_expression(node, context, useprefix, false);
    +}
    +
    +/*
    + * Show a qualifier expression for extended stats
    + */
    +static char *
    +show_stat_qual(List *qual, int is_or,
    +			   PlanState *planstate, List *ancestors,
    +			   bool useprefix, ExplainState *es)
    +{
    +	Node	   *node;
    +
    +	/* No work if empty qual */
    +	if (qual == NIL)
    +		return NULL;
    +
    +	/* Convert AND list to explicit AND */
    +	switch (is_or)
    +	{
    +		case 0:
    +			node = (Node *) make_ands_explicit(qual);
    +			break;
    +		case 1:
    +			node = (Node *) make_ors_explicit(qual);
    +			break;
    +		case 2:
    +			/* Extended stats for GROUP BY clause should be comma separeted string */
    +			node = (Node *) qual;
    +			break;
    +		default:
    +			elog(ERROR, "unexpected value: %d", is_or);
    +			break;
    +	}
    +
    +	/* And show it */
    +	return deparse_stat_expression(node, planstate, ancestors, useprefix, es);
    +}
    +
    +/*
    + * Show applied statistics for scan/agg/group plan node
    + */
    +static void
    +show_scan_stats(List *stats, List *clauses, List *ors,
    +				PlanState *planstate, List *ancestors, ExplainState *es)
    +{
    +	ListCell   *lc1, *lc2, *lc3;
    +	StringInfoData	str;
    +	bool		useprefix;
    +
    +	useprefix = es->verbose;
    +
    +	forthree (lc1, stats, lc2, clauses, lc3, ors)
    +	{
    +		StatisticExtInfo   *stat = (StatisticExtInfo *) lfirst(lc1);
    +		List   *applied_clauses = (List *) lfirst(lc2);
    +		int		is_or = lfirst_int(lc3);
    +
    +		initStringInfo(&str);
    +
    +		if (useprefix)
    +			appendStringInfo(&str, "%s.",
    +							 get_namespace_name(get_statistics_namespace(stat->statOid)));
    +
    +		appendStringInfo(&str, "%s  Clauses: %s",
    +						 get_statistics_name(stat->statOid),
    +						 show_stat_qual(applied_clauses, is_or, planstate, ancestors, useprefix, es));
    +
    +		ExplainPropertyText("Ext Stats", str.data, es);
    +	}
    +}
    +
     /*
      * Show a qualifier expression for an upper-level plan node
      */
    diff --git a/src/backend/commands/explain_state.c b/src/backend/commands/explain_state.c
    index 60d98d63a62..5d1ff461a7d 100644
    --- a/src/backend/commands/explain_state.c
    +++ b/src/backend/commands/explain_state.c
    @@ -103,6 +103,8 @@ ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
     			es->settings = defGetBoolean(opt);
     		else if (strcmp(opt->defname, "generic_plan") == 0)
     			es->generic = defGetBoolean(opt);
    +		else if (strcmp(opt->defname, "stats") == 0)
    +			es->stats = defGetBoolean(opt);
     		else if (strcmp(opt->defname, "timing") == 0)
     		{
     			timing_set = true;
    diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
    index e2d9e9be41a..fe021f35e05 100644
    --- a/src/backend/nodes/makefuncs.c
    +++ b/src/backend/nodes/makefuncs.c
    @@ -806,6 +806,17 @@ make_ands_explicit(List *andclauses)
     		return make_andclause(andclauses);
     }
     
    +Expr *
    +make_ors_explicit(List *orclauses)
    +{
    +	if (orclauses == NIL)
    +		return (Expr *) makeBoolConst(true, false);
    +	else if (list_length(orclauses) == 1)
    +		return (Expr *) linitial(orclauses);
    +	else
    +		return make_orclause(orclauses);
    +}
    +
     List *
     make_ands_implicit(Expr *clause)
     {
    diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
    index bfefc7dbea1..fbcf64ef264 100644
    --- a/src/backend/optimizer/plan/createplan.c
    +++ b/src/backend/optimizer/plan/createplan.c
    @@ -5561,6 +5561,8 @@ order_qual_clauses(PlannerInfo *root, List *clauses)
     static void
     copy_generic_path_info(Plan *dest, Path *src)
     {
    +	ListCell *lc;
    +
     	dest->disabled_nodes = src->disabled_nodes;
     	dest->startup_cost = src->startup_cost;
     	dest->total_cost = src->total_cost;
    @@ -5568,6 +5570,21 @@ copy_generic_path_info(Plan *dest, Path *src)
     	dest->plan_width = src->pathtarget->width;
     	dest->parallel_aware = src->parallel_aware;
     	dest->parallel_safe = src->parallel_safe;
    +
    +	/* Is this the right place to use makeNode()? */
    +	dest->app_extstats = makeNode(Applied_ExtStats);
    +	dest->app_extstats->applied_stats = src->parent->applied_stats;
    +	dest->app_extstats->applied_clauses_or = src->parent->applied_clauses_or;
    +	dest->app_extstats->applied_clauses = NIL;
    +
    +	foreach (lc, src->parent->applied_clauses)
    +	{
    +		List *clauses = (List *) lfirst(lc);
    +
    +		dest->app_extstats->applied_clauses
    +			= lappend(dest->app_extstats->applied_clauses,
    +					  maybe_extract_actual_clauses(clauses, false));
    +	}
     }
     
     /*
    diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
    index ff507331a06..b46ad85cba4 100644
    --- a/src/backend/optimizer/util/relnode.c
    +++ b/src/backend/optimizer/util/relnode.c
    @@ -287,6 +287,10 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
     	rel->partexprs = NULL;
     	rel->nullable_partexprs = NULL;
     
    +	rel->applied_stats = NIL;
    +	rel->applied_clauses = NIL;
    +	rel->applied_clauses_or = NIL;
    +
     	/*
     	 * Pass assorted information down the inheritance hierarchy.
     	 */
    @@ -769,6 +773,10 @@ build_join_rel(PlannerInfo *root,
     	joinrel->partexprs = NULL;
     	joinrel->nullable_partexprs = NULL;
     
    +	joinrel->applied_stats = NIL;
    +	joinrel->applied_clauses = NIL;
    +	joinrel->applied_clauses_or = NIL;
    +
     	/* Compute information relevant to the foreign relations. */
     	set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
     
    @@ -953,6 +961,10 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
     	joinrel->partexprs = NULL;
     	joinrel->nullable_partexprs = NULL;
     
    +	joinrel->applied_stats = NIL;
    +	joinrel->applied_clauses = NIL;
    +	joinrel->applied_clauses_or = NIL;
    +
     	/* Compute information relevant to foreign relations. */
     	set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
     
    diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
    index a80083d2323..b6cc5d7c2e2 100644
    --- a/src/backend/optimizer/util/restrictinfo.c
    +++ b/src/backend/optimizer/util/restrictinfo.c
    @@ -499,6 +499,41 @@ extract_actual_clauses(List *restrictinfo_list,
     	return result;
     }
     
    +/*
    + * maybe_extract_actual_clauses
    + *
    + * Just like extract_actual_clauses, but does not require the clauses to
    + * already be RestrictInfo.
    + *
    + * XXX Does not handle RestrictInfos nested in OR clauses.
    + */
    +List *
    +maybe_extract_actual_clauses(List *restrictinfo_list,
    +							 bool pseudoconstant)
    +{
    +	List	   *result = NIL;
    +	ListCell   *l;
    +
    +	foreach(l, restrictinfo_list)
    +	{
    +		RestrictInfo   *rinfo;
    +		Node *node = (Node *) lfirst(l);
    +
    +		if (!IsA(node, RestrictInfo))
    +		{
    +			result = lappend(result, node);
    +			continue;
    +		}
    +
    +		rinfo = (RestrictInfo *) node;
    +
    +		if (rinfo->pseudoconstant == pseudoconstant)
    +			result = lappend(result, rinfo->clause);
    +	}
    +
    +	return result;
    +}
    +
     /*
      * extract_actual_join_clauses
      *
    diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
    index a8b63ec0884..c7367252aee 100644
    --- a/src/backend/statistics/extended_stats.c
    +++ b/src/backend/statistics/extended_stats.c
    @@ -1854,6 +1854,14 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
     			list_exprs[listidx] = NULL;
     		}
     
    +		/* add it to the list of applied stats/clauses, if this flag is true */
    +		if (root->parse->isExplain_Stats)
    +		{
    +			rel->applied_stats = lappend(rel->applied_stats, stat);
    +			rel->applied_clauses = lappend(rel->applied_clauses, stat_clauses);
    +			rel->applied_clauses_or = lappend_int(rel->applied_clauses_or, (is_or) ? 1 : 0);
    +		}
    +
     		if (is_or)
     		{
     			bool	   *or_matches = NULL;
    diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
    index 17fbfa9b410..e90937bec55 100644
    --- a/src/backend/utils/adt/selfuncs.c
    +++ b/src/backend/utils/adt/selfuncs.c
    @@ -4346,6 +4346,7 @@ estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
     		ListCell   *lc2;
     		Bitmapset  *matched = NULL;
     		AttrNumber	attnum_offset;
    +		List	   *matched_exprs = NIL;
     
     		/*
     		 * How much we need to offset the attnums? If there are no
    @@ -4393,6 +4394,9 @@ estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
     
     				matched = bms_add_member(matched, attnum);
     
    +				/* track expressions matched by this statistics */
    +				matched_exprs = lappend(matched_exprs, varinfo->var);
    +
     				found = true;
     			}
     
    @@ -4421,6 +4425,9 @@ estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
     
     					matched = bms_add_member(matched, attnum);
     
    +					/* track expressions matched by this statistics */
    +					matched_exprs = lappend(matched_exprs, expr);
    +
     					/* there should be just one matching expression */
     					break;
     				}
    @@ -4429,6 +4436,14 @@ estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
     			}
     		}
     
    +		/* add it to the list of applied stats/clauses, if this flag is true */
    +		if (root->parse->isExplain_Stats)
    +		{
    +			rel->applied_stats = lappend(rel->applied_stats, matched_info);
    +			rel->applied_clauses = lappend(rel->applied_clauses, matched_exprs);
    +			rel->applied_clauses_or = lappend_int(rel->applied_clauses_or, 2); /* 2: Use comma to deparse */
    +		}
    +
     		/* Find the specific item that exactly matches the combination */
     		for (i = 0; i < stats->nitems; i++)
     		{
    diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
    index c460a72b75d..562ee4a8f9c 100644
    --- a/src/backend/utils/cache/lsyscache.c
    +++ b/src/backend/utils/cache/lsyscache.c
    @@ -36,6 +36,7 @@
     #include "catalog/pg_publication.h"
     #include "catalog/pg_range.h"
     #include "catalog/pg_statistic.h"
    +#include "catalog/pg_statistic_ext.h"
     #include "catalog/pg_subscription.h"
     #include "catalog/pg_transform.h"
     #include "catalog/pg_type.h"
    @@ -3854,3 +3855,51 @@ get_subscription_name(Oid subid, bool missing_ok)
     
     	return subname;
     }
    +
    +/*
    + * get_statistics_name
    + *		Returns the name of a given extended statistics
    + *
    + * Returns a palloc'd copy of the string, or NULL if no such name.
    + */
    +char *
    +get_statistics_name(Oid stxid)
    +{
    +	HeapTuple	tp;
    +
    +	tp = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxid));
    +	if (HeapTupleIsValid(tp))
    +	{
    +		Form_pg_statistic_ext stxtup = (Form_pg_statistic_ext) GETSTRUCT(tp);
    +		char	   *result;
    +
    +		result = pstrdup(NameStr(stxtup->stxname));
    +		ReleaseSysCache(tp);
    +		return result;
    +	}
    +	else
    +		return NULL;
    +}
    +
    +/*
    + * get_statistics_namespace
    + *		Returns the namespace OID of a given extended statistics
    + */
    +Oid
    +get_statistics_namespace(Oid stxid)
    +{
    +	HeapTuple	tp;
    +
    +	tp = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxid));
    +	if (HeapTupleIsValid(tp))
    +	{
    +		Form_pg_statistic_ext stxtup = (Form_pg_statistic_ext) GETSTRUCT(tp);
    +		Oid		result;
    +
    +		result = stxtup->stxnamespace;
    +		ReleaseSysCache(tp);
    +		return result;
    +	}
    +	else
    +		return InvalidOid;
    +}
    diff --git a/src/include/commands/explain_state.h b/src/include/commands/explain_state.h
    index 32728f5d1a1..10b4103db83 100644
    --- a/src/include/commands/explain_state.h
    +++ b/src/include/commands/explain_state.h
    @@ -55,6 +55,7 @@ typedef struct ExplainState
     	bool		memory;			/* print planner's memory usage information */
     	bool		settings;		/* print modified settings */
     	bool		generic;		/* generate a generic plan */
    +	bool		stats;			/* print applied extended stats */
     	ExplainSerializeOption serialize;	/* serialize the query's output? */
     	ExplainFormat format;		/* output format */
     	/* state for output formatting --- not reset for each new plan tree */
    diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
    index 5473ce9a288..b32aa762c9b 100644
    --- a/src/include/nodes/makefuncs.h
    +++ b/src/include/nodes/makefuncs.h
    @@ -95,6 +95,8 @@ extern Node *make_and_qual(Node *qual1, Node *qual2);
     extern Expr *make_ands_explicit(List *andclauses);
     extern List *make_ands_implicit(Expr *clause);
     
    +extern Expr *make_ors_explicit(List *orclauses);
    +
     extern IndexInfo *makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid,
     								List *expressions, List *predicates,
     								bool unique, bool nulls_not_distinct,
    diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
    index 86a236bd58b..acadd843d69 100644
    --- a/src/include/nodes/parsenodes.h
    +++ b/src/include/nodes/parsenodes.h
    @@ -254,6 +254,9 @@ typedef struct Query
     	ParseLoc	stmt_location;
     	/* length in bytes; 0 means "rest of string" */
     	ParseLoc	stmt_len pg_node_attr(query_jumble_ignore);
    +
    +	/* if true, query is explain with stats option */
    +	bool        isExplain_Stats;
     } Query;
     
     
    diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
    index ad2726f026f..081546cc771 100644
    --- a/src/include/nodes/pathnodes.h
    +++ b/src/include/nodes/pathnodes.h
    @@ -1075,6 +1075,11 @@ typedef struct RelOptInfo
     	List	  **partexprs pg_node_attr(read_write_ignore);
     	/* Nullable partition key expressions */
     	List	  **nullable_partexprs pg_node_attr(read_write_ignore);
    +
    +	/* info about applied extended statistics */
    +	List       *applied_stats;		/* list of StatisticExtInfo */
    +	List       *applied_clauses;	/* list of lists of clauses */
    +	List       *applied_clauses_or;	/* are the clauses AND, OR, or Comma */
     } RelOptInfo;
     
     /*
    diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
    index 29d7732d6a0..aba7b29eb21 100644
    --- a/src/include/nodes/plannodes.h
    +++ b/src/include/nodes/plannodes.h
    @@ -239,6 +239,9 @@ typedef struct Plan
     	 */
     	Bitmapset  *extParam;
     	Bitmapset  *allParam;
    +
    +	/* info about applied extended statistics */
    +	struct Applied_ExtStats *app_extstats;
     } Plan;
     
     /* ----------------
    @@ -1792,4 +1795,16 @@ typedef enum MonotonicFunction
     	MONOTONICFUNC_BOTH = MONOTONICFUNC_INCREASING | MONOTONICFUNC_DECREASING,
     } MonotonicFunction;
     
    +/*
    + * Applied_ExtStats - Information to show applied Extend Statistics
    + *
    + */
    +typedef struct Applied_ExtStats
    +{
    +	NodeTag	type;
    +	List    *applied_stats;
    +	List    *applied_clauses;
    +	List    *applied_clauses_or;
    +} Applied_ExtStats;
    +
     #endif							/* PLANNODES_H */
    diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h
    index ec91fc9c583..0c34af43138 100644
    --- a/src/include/optimizer/restrictinfo.h
    +++ b/src/include/optimizer/restrictinfo.h
    @@ -50,6 +50,8 @@ extern bool restriction_is_securely_promotable(RestrictInfo *restrictinfo,
     extern List *get_actual_clauses(List *restrictinfo_list);
     extern List *extract_actual_clauses(List *restrictinfo_list,
     									bool pseudoconstant);
    +extern List *maybe_extract_actual_clauses(List *restrictinfo_list,
    +										  bool pseudoconstant);
     extern void extract_actual_join_clauses(List *restrictinfo_list,
     										Relids joinrelids,
     										List **joinquals,
    diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
    index fa7c7e0323b..88b8f9c1bd6 100644
    --- a/src/include/utils/lsyscache.h
    +++ b/src/include/utils/lsyscache.h
    @@ -211,6 +211,9 @@ extern char *get_publication_name(Oid pubid, bool missing_ok);
     extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
     extern char *get_subscription_name(Oid subid, bool missing_ok);
     
    +extern char *get_statistics_name(Oid stxid);
    +extern Oid	get_statistics_namespace(Oid stxid);
    +
     #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
     /* type_is_array_domain accepts both plain arrays and domains over arrays */
     #define type_is_array_domain(typid)  (get_base_element_type(typid) != InvalidOid)
    -- 
    2.39.5
    
    
    --wy3zu232wgf6pkqj
    Content-Type: text/x-diff; charset=utf-8
    Content-Disposition: attachment;
    	filename="0002-Add-a-new-option-auto_explain.log_stats-to-auto_expl.patch"