Add an alternative_plan_name field to PlannerInfo.

Typically, we have only one PlannerInfo for any given subquery, but
when we are considering a MinMaxAggPath or a hashed subplan, we end
up creating a second PlannerInfo for the same portion of the query,
with a clone of the original range table. In fact, in the MinMaxAggPath
case, we might end up creating several clones, one per aggregate.

At present, there's no easy way for a plugin, such as pg_plan_advice,
to understand the relationships between the original range table and
the copies of it that are created in these cases.  To fix, add an
alternative_plan_name field to PlannerInfo. For a hashed subplan, this
is the plan name for the non-hashed alternative; for minmax aggregates,
this is the plan_name from the parent PlannerInfo; otherwise, it's the
same as plan_name.

Discussion: http://postgr.es/m/CA+TgmoYuWmN-00Ec5pY7zAcpSFQUQLbgAdVWGR9kOR-HM-fHrA@mail.gmail.com
Reviewed-by: Lukas Fittl <lukas@fittl.com>
This commit is contained in:
Robert Haas 2026-03-26 16:45:17 -04:00
parent 10e2a8ac6a
commit 26255a3207
8 changed files with 31 additions and 9 deletions

View file

@ -2833,7 +2833,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Generate a subroot and Paths for the subquery */
plan_name = choose_plan_name(root->glob, rte->eref->aliasname, false);
rel->subroot = subquery_planner(root->glob, subquery, plan_name,
root, false, tuple_fraction, NULL);
root, NULL, false, tuple_fraction, NULL);
/* Isolate the params needed by this specific subplan */
rel->subplan_params = root->plan_params;

View file

@ -341,6 +341,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
subroot->query_level++;
subroot->parent_root = root;
subroot->plan_name = choose_plan_name(root->glob, "minmax", true);
subroot->alternative_plan_name = root->plan_name;
/* reset subplan-related stuff */
subroot->plan_params = NIL;

View file

@ -515,8 +515,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
&tuple_fraction, es);
/* primary planning entry point (may recurse for subqueries) */
root = subquery_planner(glob, parse, NULL, NULL, false, tuple_fraction,
NULL);
root = subquery_planner(glob, parse, NULL, NULL, NULL, false,
tuple_fraction, NULL);
/* Select best Path and turn it into a Plan */
final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
@ -715,6 +715,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
* parse is the querytree produced by the parser & rewriter.
* plan_name is the name to assign to this subplan (NULL at the top level).
* parent_root is the immediate parent Query's info (NULL at the top level).
* alternative_root is a previously created PlannerInfo for which this query
* level is an alternative implementation, or else NULL.
* hasRecursion is true if this is a recursive WITH query.
* tuple_fraction is the fraction of tuples we expect will be retrieved.
* tuple_fraction is interpreted as explained for grouping_planner, below.
@ -741,8 +743,9 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
PlannerInfo *
subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
PlannerInfo *parent_root, bool hasRecursion,
double tuple_fraction, SetOperationStmt *setops)
PlannerInfo *parent_root, PlannerInfo *alternative_root,
bool hasRecursion, double tuple_fraction,
SetOperationStmt *setops)
{
PlannerInfo *root;
List *newWithCheckOptions;
@ -758,6 +761,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
root->glob = glob;
root->query_level = parent_root ? parent_root->query_level + 1 : 1;
root->plan_name = plan_name;
if (alternative_root != NULL)
root->alternative_plan_name = alternative_root->plan_name;
else
root->alternative_plan_name = plan_name;
root->parent_root = parent_root;
root->plan_params = NIL;
root->outer_params = NULL;

View file

@ -224,7 +224,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
/* Generate Paths for the subquery */
subroot = subquery_planner(root->glob, subquery,
choose_plan_name(root->glob, sublinkstr, true),
root, false, tuple_fraction, NULL);
root, NULL, false, tuple_fraction, NULL);
/* Isolate the params needed by this specific subplan */
plan_params = root->plan_params;
@ -274,7 +274,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
/* Generate Paths for the ANY subquery; we'll need all rows */
plan_name = choose_plan_name(root->glob, sublinkstr, true);
subroot = subquery_planner(root->glob, subquery, plan_name,
root, false, 0.0, NULL);
root, subroot, false, 0.0, NULL);
/* Isolate the params needed by this specific subplan */
plan_params = root->plan_params;
@ -971,7 +971,7 @@ SS_process_ctes(PlannerInfo *root)
*/
subroot = subquery_planner(root->glob, subquery,
choose_plan_name(root->glob, cte->ctename, false),
root, cte->cterecursive, 0.0, NULL);
root, NULL, cte->cterecursive, 0.0, NULL);
/*
* Since the current query level doesn't yet contain any RTEs, it

View file

@ -1418,6 +1418,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->glob = root->glob;
subroot->query_level = root->query_level;
subroot->plan_name = root->plan_name;
subroot->alternative_plan_name = root->alternative_plan_name;
subroot->parent_root = root->parent_root;
subroot->plan_params = NIL;
subroot->outer_params = NULL;

View file

@ -250,7 +250,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
*/
plan_name = choose_plan_name(root->glob, "setop", true);
subroot = rel->subroot = subquery_planner(root->glob, subquery,
plan_name, root,
plan_name, root, NULL,
false, root->tuple_fraction,
parentOp);

View file

@ -320,6 +320,18 @@ struct PlannerInfo
/* Subplan name for EXPLAIN and debugging purposes (NULL at top level) */
char *plan_name;
/*
* If this PlannerInfo exists to consider an alternative implementation
* strategy for a portion of the query that could also be implemented by
* some other PlannerInfo, this is the plan_name for that other
* PlannerInfo. When we are considering the first or only alternative,
* it is the same as plan_name.
*
* Currently, we set this to a value other than plan_name only when
* considering a MinMaxAggPath or a hashed SubPlan.
*/
char *alternative_plan_name;
/*
* plan_params contains the expressions that this query level needs to
* make available to a lower query level that is currently being planned.

View file

@ -63,6 +63,7 @@ extern PlannedStmt *standard_planner(Query *parse, const char *query_string,
extern PlannerInfo *subquery_planner(PlannerGlobal *glob, Query *parse,
char *plan_name,
PlannerInfo *parent_root,
PlannerInfo *alternative_root,
bool hasRecursion, double tuple_fraction,
SetOperationStmt *setops);