From fccb5880f77e441dd3514d962dea73e4c5932fb0 Mon Sep 17 00:00:00 2001 From: shubhankar Date: Fri, 9 Oct 2026 16:13:13 +0530 Subject: [PATCH v1] Don't let flat-copied planner structures share extension state build_grouped_rel(), create_unique_paths(), create_partial_unique_paths() and build_minmax_path() make a flat copy of a RelOptInfo or PlannerInfo and then reset the fields that must not be inherited. None of them resets extension_state or extension_state_allocated, so the copy and the original point at the same private-state array. joinrel_setup_hook and build_simple_rel_hook give extensions a natural place to attach state to a RelOptInfo as soon as it is built, so this has visible consequences. An extension that stores state on a joinrel in joinrel_setup_hook gets that same state back for the grouped joinrel that eager aggregation copies from it, and anything it stores for the grouped joinrel overwrites the plain joinrel's entry. If the store has to enlarge the array, SetRelOptInfoExtensionState() repallocs it, freeing the array the plain joinrel still points to, and the next lookup on the plain joinrel reads freed memory. Fix by resetting the extension state in each of the four copies, so a copy starts with no private state. The copy in build_minmax_path() is not reachable through any current hook, because it is made before any hook can attach state to the parent PlannerInfo, but reset it too for consistency. While here, document that GetPlannerExtensionId() keeps the caller's pointer, as GetExplainExtensionId() already does. Co-Authored-By: Claude Opus 5.5 --- src/backend/optimizer/plan/planagg.c | 4 ++++ src/backend/optimizer/plan/planner.c | 12 ++++++++++++ src/backend/optimizer/util/extendplan.c | 3 +++ src/backend/optimizer/util/relnode.c | 6 ++++++ 4 files changed, 25 insertions(+) diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index 369d713b70..8a4548db84 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -350,6 +350,10 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, subroot->agginfos = NIL; subroot->aggtransinfos = NIL; + /* don't share the parent's extension state array */ + subroot->extension_state = NULL; + subroot->extension_state_allocated = 0; + subroot->parse = parse = copyObject(root->parse); IncrementVarSublevelsUp((Node *) parse, 1, 1); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index edaa42d713..fa720b077e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8756,6 +8756,12 @@ create_unique_paths(PlannerInfo *root, RelOptInfo *rel, SpecialJoinInfo *sjinfo) unique_rel->cheapest_total_path = NULL; unique_rel->cheapest_parameterized_paths = NIL; + /* + * clear extension state; the array belongs to the input rel + */ + unique_rel->extension_state = NULL; + unique_rel->extension_state_allocated = 0; + /* * Build the target list for the unique rel. We also build the pathkeys * that represent the ordering requirements for the sort-based @@ -9130,6 +9136,12 @@ create_partial_unique_paths(PlannerInfo *root, RelOptInfo *input_rel, partial_unique_rel->cheapest_total_path = NULL; partial_unique_rel->cheapest_parameterized_paths = NIL; + /* + * clear extension state; the array belongs to the input rel + */ + partial_unique_rel->extension_state = NULL; + partial_unique_rel->extension_state_allocated = 0; + /* Estimate number of output rows */ partial_unique_rel->rows = estimate_num_groups(root, sjinfo->semi_rhs_exprs, diff --git a/src/backend/optimizer/util/extendplan.c b/src/backend/optimizer/util/extendplan.c index e2d077b7b0..30458f6561 100644 --- a/src/backend/optimizer/util/extendplan.c +++ b/src/backend/optimizer/util/extendplan.c @@ -36,6 +36,9 @@ static int PlannerExtensionNamesAllocated = 0; * to the same ID every time. IDs are not stable across backends. Use the ID * that you get from this function to call the remaining functions in this * file. + * + * extension_name is assumed to be a constant string or allocated in storage + * that will never be freed. */ int GetPlannerExtensionId(const char *extension_name) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 34dfb75715..6b1527c31a 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -516,6 +516,12 @@ build_grouped_rel(PlannerInfo *root, RelOptInfo *rel) grouped_rel->cheapest_total_path = NULL; grouped_rel->cheapest_parameterized_paths = NIL; + /* + * clear extension state; the array belongs to the input rel + */ + grouped_rel->extension_state = NULL; + grouped_rel->extension_state_allocated = 0; + /* * clear partition info */ -- 2.43.0