diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index b970997..b351220 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -78,6 +78,9 @@ 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 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); @@ -1742,6 +1745,10 @@ ExplainNode(PlanState *planstate, List *ancestors, if (plan->qual) show_instrumentation_count("Rows Removed by Filter", 1, planstate, es); + if (es->verbose) + show_scan_stats(plan->applied_stats, plan->applied_clauses, + plan->applied_clauses_or, + planstate, ancestors, es); break; case T_IndexOnlyScan: show_scan_qual(((IndexOnlyScan *) plan)->indexqual, @@ -1758,10 +1765,18 @@ ExplainNode(PlanState *planstate, List *ancestors, if (es->analyze) ExplainPropertyFloat("Heap Fetches", NULL, planstate->instrument->ntuples2, 0, es); + if (es->verbose) + show_scan_stats(plan->applied_stats, plan->applied_clauses, + plan->applied_clauses_or, + planstate, ancestors, es); break; case T_BitmapIndexScan: show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig, "Index Cond", planstate, ancestors, es); + if (es->verbose) + show_scan_stats(plan->applied_stats, plan->applied_clauses, + plan->applied_clauses_or, + planstate, ancestors, es); break; case T_BitmapHeapScan: show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig, @@ -1791,6 +1806,10 @@ ExplainNode(PlanState *planstate, List *ancestors, if (plan->qual) show_instrumentation_count("Rows Removed by Filter", 1, planstate, es); + if (es->verbose) + show_scan_stats(plan->applied_stats, plan->applied_clauses, + plan->applied_clauses_or, + planstate, ancestors, es); break; case T_Gather: { @@ -1978,6 +1997,10 @@ ExplainNode(PlanState *planstate, List *ancestors, if (plan->qual) show_instrumentation_count("Rows Removed by Filter", 1, planstate, es); + if (es->verbose) + show_scan_stats(plan->applied_stats, plan->applied_clauses, + plan->applied_clauses_or, + planstate, ancestors, es); break; case T_Group: show_group_keys(castNode(GroupState, planstate), ancestors, es); @@ -1985,6 +2008,10 @@ ExplainNode(PlanState *planstate, List *ancestors, if (plan->qual) show_instrumentation_count("Rows Removed by Filter", 1, planstate, es); + if (es->verbose) + show_scan_stats(plan->applied_stats, plan->applied_clauses, + plan->applied_clauses_or, + planstate, ancestors, es); break; case T_Sort: show_sort_keys(castNode(SortState, planstate), ancestors, es); @@ -2305,6 +2332,76 @@ show_scan_qual(List *qual, const char *qlabel, } /* + * Show a generic expression + */ +static char * +deparse_stat_expression(Node *node, + PlanState *planstate, List *ancestors, + 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, false, false); +} + +/* + * Show a qualifier expression (which is a List with implicit AND semantics) + */ +static char * +show_stat_qual(List *qual, bool is_or, + PlanState *planstate, List *ancestors, + ExplainState *es) +{ + Node *node; + + /* No work if empty qual */ + if (qual == NIL) + return NULL; + + /* Convert AND list to explicit AND */ + if (is_or) + node = (Node *) make_ors_explicit(qual); + else + node = (Node *) make_ands_explicit(qual); + + /* And show it */ + return deparse_stat_expression(node, planstate, ancestors, es); +} + +/* + * Show applied statistics for scan 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; + + forthree (lc1, stats, lc2, clauses, lc3, ors) + { + StatisticExtInfo *stat = (StatisticExtInfo *) lfirst(lc1); + List *clauses = (List *) lfirst(lc2); + int is_or = lfirst_int(lc3); + + initStringInfo(&str); + + appendStringInfo(&str, "%s.%s Clauses: %s", + get_namespace_name(get_statistics_namespace(stat->statOid)), + get_statistics_name(stat->statOid), + show_stat_qual(clauses, is_or, planstate, ancestors, es)); + + ExplainPropertyText("Statistics", str.data, es); + } +} + +/* * Show a qualifier expression for an upper-level plan node */ static void diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 90b5da5..654c4b5 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -136,6 +136,10 @@ CopyPlanFields(const Plan *from, Plan *newnode) COPY_NODE_FIELD(initPlan); COPY_BITMAPSET_FIELD(extParam); COPY_BITMAPSET_FIELD(allParam); + + COPY_NODE_FIELD(applied_stats); + COPY_NODE_FIELD(applied_clauses); + COPY_NODE_FIELD(applied_clauses_or); } /* diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 8223956..26b9255 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -715,6 +715,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/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 2b02369..6a6e20c 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -349,6 +349,10 @@ _outPlanInfo(StringInfo str, const Plan *node) WRITE_NODE_FIELD(initPlan); WRITE_BITMAPSET_FIELD(extParam); WRITE_BITMAPSET_FIELD(allParam); + + WRITE_NODE_FIELD(applied_stats); + WRITE_NODE_FIELD(applied_clauses); + WRITE_NODE_FIELD(applied_clauses_or); } /* diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 3f68f7c..7220948 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1627,6 +1627,10 @@ ReadCommonPlan(Plan *local_node) READ_NODE_FIELD(initPlan); READ_BITMAPSET_FIELD(extParam); READ_BITMAPSET_FIELD(allParam); + + READ_NODE_FIELD(applied_stats); + READ_NODE_FIELD(applied_clauses); + READ_NODE_FIELD(applied_clauses_or); } /* diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index cd6d72c..9efb961 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -5294,12 +5294,27 @@ order_qual_clauses(PlannerInfo *root, List *clauses) static void copy_generic_path_info(Plan *dest, Path *src) { + ListCell *lc; + dest->startup_cost = src->startup_cost; dest->total_cost = src->total_cost; dest->plan_rows = src->rows; dest->plan_width = src->pathtarget->width; dest->parallel_aware = src->parallel_aware; dest->parallel_safe = src->parallel_safe; + + dest->applied_stats = src->parent->applied_stats; + dest->applied_clauses_or = src->parent->applied_clauses_or; + + dest->applied_clauses = NIL; + foreach (lc, src->parent->applied_clauses) + { + List *clauses = (List *) lfirst(lc); + + dest->applied_clauses + = lappend(dest->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 520409f..6a4e1c1 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -260,6 +260,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. */ @@ -675,6 +679,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); @@ -854,6 +862,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; + joinrel->top_parent_relids = bms_union(outer_rel->top_parent_relids, inner_rel->top_parent_relids); diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index ef8df3d..cd982c3 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -459,6 +459,41 @@ extract_actual_clauses(List *restrictinfo_list, } /* + * 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 * * Extract bare clauses from 'restrictinfo_list', separating those that diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index 87fe82e..3606e35 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -1829,6 +1829,11 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli list_exprs[listidx] = NULL; } + /* add it to the list of applied stats/clauses */ + 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 1fbb0b2..c03a2e3 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -4019,6 +4019,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 @@ -4066,6 +4067,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; } @@ -4094,6 +4098,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; } @@ -4102,6 +4109,10 @@ estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel, } } + rel->applied_stats = lappend(rel->applied_stats, matched_info); + rel->applied_clauses = lappend(rel->applied_clauses, matched_exprs); + rel->applied_clauses_or = lappend(rel->applied_clauses_or, 0); + /* 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 feef999..3363be4 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -33,6 +33,7 @@ #include "catalog/pg_proc.h" #include "catalog/pg_range.h" #include "catalog/pg_statistic.h" +#include "catalog/pg_statistic_ext.h" #include "catalog/pg_transform.h" #include "catalog/pg_type.h" #include "miscadmin.h" @@ -3568,3 +3569,51 @@ get_index_isclustered(Oid index_oid) return isclustered; } + +/* + * get_statistics_name + * Returns the name of a given extended statistics + * + * Returns a palloc'd copy of the string, or NULL if no such namespace. + */ +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/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index fe17310..249833d 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -94,6 +94,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 isready, bool concurrent); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 1f3845b..6abfea6 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -773,6 +773,11 @@ typedef struct RelOptInfo Relids all_partrels; /* Relids set of all partition relids */ List **partexprs; /* Non-nullable partition key expressions */ List **nullable_partexprs; /* Nullable partition key expressions */ + + /* 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 */ } RelOptInfo; /* diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 0b518ce..425d659 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -158,6 +158,11 @@ typedef struct Plan */ Bitmapset *extParam; Bitmapset *allParam; + + /* info about applied statistics */ + List *applied_stats; + List *applied_clauses; + List *applied_clauses_or; } Plan; /* ---------------- diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index 6d30bd5..fc27723 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -37,6 +37,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 b8dd27d..a9c206a 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -199,6 +199,9 @@ extern bool get_index_isreplident(Oid index_oid); extern bool get_index_isvalid(Oid index_oid); extern bool get_index_isclustered(Oid index_oid); +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)