From 6e17282a078941a669d4e0da720d9642daeee4b1 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 20 Aug 2025 15:10:52 -0400
Subject: [PATCH v3 2/5] Remove PlannerInfo's join_search_private method.

Instead, use the new mechanism that allows planner extensions to store
private state inside a PlannerInfo, treating GEQO as an in-core planner
extension.  This is a useful test of the new facility, and also buys
back a few bytes of storage.

To make this work, we must remove innerrel_is_unique_ext's hack of
testing whether join_search_private is set as a proxy for whether
the join search might be retried. Add a flag that extensions can
use to explicitly signal their intentions instead.
---
 src/backend/optimizer/geqo/geqo_eval.c    |  2 +-
 src/backend/optimizer/geqo/geqo_main.c    | 12 ++++++++++--
 src/backend/optimizer/geqo/geqo_random.c  |  7 +++----
 src/backend/optimizer/plan/analyzejoins.c |  9 +++------
 src/backend/optimizer/plan/planner.c      |  1 +
 src/backend/optimizer/prep/prepjointree.c |  1 +
 src/include/nodes/pathnodes.h             |  5 ++---
 src/include/optimizer/geqo.h              | 10 +++++++++-
 8 files changed, 30 insertions(+), 17 deletions(-)

diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
index f07d1dc8ac6..7fcb1aa70d1 100644
--- a/src/backend/optimizer/geqo/geqo_eval.c
+++ b/src/backend/optimizer/geqo/geqo_eval.c
@@ -162,7 +162,7 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
 RelOptInfo *
 gimme_tree(PlannerInfo *root, Gene *tour, int num_gene)
 {
-	GeqoPrivateData *private = (GeqoPrivateData *) root->join_search_private;
+	GeqoPrivateData *private = GetGeqoPrivateData(root);
 	List	   *clumps;
 	int			rel_count;
 
diff --git a/src/backend/optimizer/geqo/geqo_main.c b/src/backend/optimizer/geqo/geqo_main.c
index 38402ce58db..0064556087a 100644
--- a/src/backend/optimizer/geqo/geqo_main.c
+++ b/src/backend/optimizer/geqo/geqo_main.c
@@ -47,6 +47,8 @@ int			Geqo_generations;
 double		Geqo_selection_bias;
 double		Geqo_seed;
 
+/* GEQO is treated as an in-core planner extension */
+int			Geqo_planner_extension_id = -1;
 
 static int	gimme_pool_size(int nr_rel);
 static int	gimme_number_generations(int pool_size);
@@ -98,10 +100,16 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels)
 	int			mutations = 0;
 #endif
 
+	if (Geqo_planner_extension_id < 0)
+		Geqo_planner_extension_id = GetPlannerExtensionId("geqo");
+
 /* set up private information */
-	root->join_search_private = &private;
+	SetPlannerInfoExtensionState(root, Geqo_planner_extension_id, &private);
 	private.initial_rels = initial_rels;
 
+/* inform core planner that we may replan */
+	root->assumeReplanning = true;
+
 /* initialize private number generator */
 	geqo_set_seed(root, Geqo_seed);
 
@@ -304,7 +312,7 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels)
 	free_pool(root, pool);
 
 	/* ... clear root pointer to our private storage */
-	root->join_search_private = NULL;
+	SetPlannerInfoExtensionState(root, Geqo_planner_extension_id, NULL);
 
 	return best_rel;
 }
diff --git a/src/backend/optimizer/geqo/geqo_random.c b/src/backend/optimizer/geqo/geqo_random.c
index 6c7a411f69f..46d28baa2e6 100644
--- a/src/backend/optimizer/geqo/geqo_random.c
+++ b/src/backend/optimizer/geqo/geqo_random.c
@@ -15,11 +15,10 @@
 
 #include "optimizer/geqo_random.h"
 
-
 void
 geqo_set_seed(PlannerInfo *root, double seed)
 {
-	GeqoPrivateData *private = (GeqoPrivateData *) root->join_search_private;
+	GeqoPrivateData *private = GetGeqoPrivateData(root);
 
 	pg_prng_fseed(&private->random_state, seed);
 }
@@ -27,7 +26,7 @@ geqo_set_seed(PlannerInfo *root, double seed)
 double
 geqo_rand(PlannerInfo *root)
 {
-	GeqoPrivateData *private = (GeqoPrivateData *) root->join_search_private;
+	GeqoPrivateData *private = GetGeqoPrivateData(root);
 
 	return pg_prng_double(&private->random_state);
 }
@@ -35,7 +34,7 @@ geqo_rand(PlannerInfo *root)
 int
 geqo_randint(PlannerInfo *root, int upper, int lower)
 {
-	GeqoPrivateData *private = (GeqoPrivateData *) root->join_search_private;
+	GeqoPrivateData *private = GetGeqoPrivateData(root);
 
 	/*
 	 * In current usage, "lower" is never negative so we can just use
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index da92d8ee414..60b37ac6cd0 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -1424,17 +1424,14 @@ innerrel_is_unique_ext(PlannerInfo *root,
 		 *
 		 * However, in normal planning mode, caching this knowledge is totally
 		 * pointless; it won't be queried again, because we build up joinrels
-		 * from smaller to larger.  It is useful in GEQO mode, where the
-		 * knowledge can be carried across successive planning attempts; and
-		 * it's likely to be useful when using join-search plugins, too. Hence
-		 * cache when join_search_private is non-NULL.  (Yeah, that's a hack,
-		 * but it seems reasonable.)
+		 * from smaller to larger.  It's only useful when using GEQO or
+		 * another planner extension that attempts planning multiple times.
 		 *
 		 * Also, allow callers to override that heuristic and force caching;
 		 * that's useful for reduce_unique_semijoins, which calls here before
 		 * the normal join search starts.
 		 */
-		if (force_cache || root->join_search_private)
+		if (force_cache || root->assumeReplanning)
 		{
 			old_context = MemoryContextSwitchTo(root->planner_cxt);
 			innerrel->non_unique_for_rels =
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 65f17101591..0ffe8cf12c6 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -703,6 +703,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root,
 	root->hasAlternativeSubPlans = false;
 	root->placeholdersFrozen = false;
 	root->hasRecursion = hasRecursion;
+	root->assumeReplanning = false;
 	if (hasRecursion)
 		root->wt_param_id = assign_special_exec_param(root);
 	else
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 35e8d3c183b..4075f7519ca 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1383,6 +1383,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->qual_security_level = 0;
 	subroot->placeholdersFrozen = false;
 	subroot->hasRecursion = false;
+	subroot->assumeReplanning = false;
 	subroot->wt_param_id = -1;
 	subroot->non_recursive_path = NULL;
 	/* We don't currently need a top JoinDomain for the subroot */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 21df16f5b04..641e77a1326 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -536,6 +536,8 @@ struct PlannerInfo
 	bool		placeholdersFrozen;
 	/* true if planning a recursive WITH item */
 	bool		hasRecursion;
+	/* true if a planner extension may replan this subquery */
+	bool		assumeReplanning;
 
 	/*
 	 * The rangetable index for the RTE_GROUP RTE, or 0 if there is no
@@ -582,9 +584,6 @@ struct PlannerInfo
 	bool	   *isAltSubplan pg_node_attr(read_write_ignore);
 	bool	   *isUsedSubplan pg_node_attr(read_write_ignore);
 
-	/* optional private data for join_search_hook, e.g., GEQO */
-	void	   *join_search_private pg_node_attr(read_write_ignore);
-
 	/* Does this query modify any partition key columns? */
 	bool		partColsUpdated;
 
diff --git a/src/include/optimizer/geqo.h b/src/include/optimizer/geqo.h
index 9f8e0f337aa..3f4872e25e3 100644
--- a/src/include/optimizer/geqo.h
+++ b/src/include/optimizer/geqo.h
@@ -24,6 +24,7 @@
 
 #include "common/pg_prng.h"
 #include "nodes/pathnodes.h"
+#include "optimizer/extendplan.h"
 #include "optimizer/geqo_gene.h"
 
 
@@ -62,6 +63,8 @@ extern PGDLLIMPORT int Geqo_generations;	/* 1 .. inf, or 0 to use default */
 
 extern PGDLLIMPORT double Geqo_selection_bias;
 
+extern PGDLLIMPORT int Geqo_planner_extension_id;
+
 #define DEFAULT_GEQO_SELECTION_BIAS 2.0
 #define MIN_GEQO_SELECTION_BIAS 1.5
 #define MAX_GEQO_SELECTION_BIAS 2.0
@@ -70,7 +73,7 @@ extern PGDLLIMPORT double Geqo_seed;	/* 0 .. 1 */
 
 
 /*
- * Private state for a GEQO run --- accessible via root->join_search_private
+ * Private state for a GEQO run --- accessible via GetGeqoPrivateData
  */
 typedef struct
 {
@@ -78,6 +81,11 @@ typedef struct
 	pg_prng_state random_state; /* PRNG state */
 } GeqoPrivateData;
 
+static inline GeqoPrivateData *
+GetGeqoPrivateData(PlannerInfo *root)
+{
+	return GetPlannerInfoExtensionState(root, Geqo_planner_extension_id);
+}
 
 /* routines in geqo_main.c */
 extern RelOptInfo *geqo(PlannerInfo *root,
-- 
2.39.5 (Apple Git-154)

