diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5d515842dc..cb397c6012 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -636,6 +636,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->wt_param_id = -1; root->non_recursive_path = NULL; root->partColsUpdated = false; + root->actual_var_ranges = NIL; /* * If there is a WITH list, process each WITH query and either convert it diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 9ff2b595a8..9672934534 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -936,6 +936,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; + subroot->actual_var_ranges = NIL; /* No CTEs to worry about */ Assert(subquery->cteList == NIL); diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index e96bfce4dc..a6e73d0b8f 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -5585,9 +5585,12 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, Datum *min, Datum *max) { bool have_data = false; + bool have_cache = false; + ActualVarRangeInfo *cache = NULL; RelOptInfo *rel = vardata->rel; RangeTblEntry *rte; ListCell *lc; + PlannerInfo *proot; /* No hope if no relation or it doesn't have indexes */ if (rel == NULL || rel->indexlist == NIL) @@ -5648,7 +5651,37 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, } /* - * Found a suitable index to extract data from. Set up some data that + * Found a suitable index to extract data from. + * Do we have the extremal values on PlannerInfo cache? + * + * If this is a subquery, traverse to the top-level PlannerInfo + * to read/store ActualVarRangeInfos. + */ + proot = root; + while (proot->parent_root != NULL) + proot = proot->parent_root; + + foreach(lc, proot->actual_var_ranges) + { + cache = (ActualVarRangeInfo *) lfirst(lc); + + if (cache->indexoid != index->indexoid) + continue; + have_cache = true; + + if (min && cache->min) + *min = datumCopy(cache->min, cache->typByVal, cache->typLen); + if (max && cache->max) + *max = datumCopy(cache->max, cache->typByVal, cache->typLen); + + if ((min && !cache->min) || (max && !cache->max)) + break; + + return have_cache; + } + + /* + * Nope, read actual range from index. Set up some data that * can be used by both invocations of get_actual_variable_endpoint. */ { @@ -5689,7 +5722,7 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, (Datum) 0); /* constant */ /* If min is requested ... */ - if (min) + if (min && (!have_cache || !cache->min)) { have_data = get_actual_variable_endpoint(heapRel, indexRel, @@ -5708,7 +5741,7 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, } /* If max is requested, and we didn't find the index is empty */ - if (max && have_data) + if (max && have_data && (!have_cache || !cache->max)) { /* scan in the opposite direction; all else is the same */ have_data = get_actual_variable_endpoint(heapRel, @@ -5731,6 +5764,36 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, MemoryContextSwitchTo(oldcontext); MemoryContextDelete(tmpcontext); + /* + * Store variable range in PlannerInfo cache for subsequent calls. + * + * If there's already a ActualVarRangeInfo for this index, just + * fill the missing information. + */ + if (have_data && !have_cache) + { + oldcontext = MemoryContextSwitchTo(proot->planner_cxt); + cache = (ActualVarRangeInfo *) palloc(sizeof(ActualVarRangeInfo)); + + cache->indexoid = index->indexoid; + cache->min = PointerGetDatum(NULL); + cache->max = PointerGetDatum(NULL); + cache->typByVal = typByVal; + cache->typLen = typLen; + + proot->actual_var_ranges = lappend(proot->actual_var_ranges, + cache); + MemoryContextSwitchTo(oldcontext); + } + + if (have_data) + { + if (min && !cache->min) + cache->min = datumCopy(*min, typByVal, typLen); + if (max && !cache->max) + cache->max = datumCopy(*max, typByVal, typLen); + } + /* And we're done */ break; } diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 69150e46eb..d92f169b76 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -362,6 +362,9 @@ struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* List of ActualVarRangeInfos, a cache for get_actual_variable_range */ + List *actual_var_ranges; }; @@ -2333,6 +2336,24 @@ typedef struct MinMaxAggInfo Param *param; /* param for subplan's output */ } MinMaxAggInfo; + +/* + * Store both extremal values of an index. + * + * The planner stores ActualVarRangeInfos in PlannerInfo to avoid repeated + * executions of get_actual_variable_range that can be very expensive, + * particularly if the index is bloated on any of the extremes. + */ +typedef struct ActualVarRangeInfo +{ + Datum min; /* index minimal value */ + Datum max; /* index maximal value */ + Oid indexoid; + int16 typLen; + bool typByVal; +} ActualVarRangeInfo; + + /* * At runtime, PARAM_EXEC slots are used to pass values around from one plan * node to another. They can be used to pass values down into subqueries (for