From 27f806771780a651211912df08b95e26c650f36d Mon Sep 17 00:00:00 2001
From: Chengpeng Yan <chengpeng_yan@outlook.com>
Date: Tue, 16 Dec 2025 20:15:44 +0800
Subject: [PATCH v4 2/2] add a GUC goo_greedy_strategy to choose
 different GOO  greedy strategic to test

Signed-off-by: Chengpeng Yan <chengpeng_yan@outlook.com>
---
 src/backend/optimizer/path/goo.c          | 122 +++++++++++++++++++++-
 src/backend/utils/misc/guc_parameters.dat |  10 ++
 src/backend/utils/misc/guc_tables.c       |   7 ++
 src/include/optimizer/paths.h             |   7 ++
 4 files changed, 145 insertions(+), 1 deletion(-)

diff --git a/src/backend/optimizer/path/goo.c b/src/backend/optimizer/path/goo.c
index 247dbb5f921..bbada782960 100644
--- a/src/backend/optimizer/path/goo.c
+++ b/src/backend/optimizer/path/goo.c
@@ -55,6 +55,7 @@
  * Configuration defaults.  These are exposed as GUCs in guc_tables.c.
  */
 bool		enable_goo_join_search = false;
+int			goo_greedy_strategy = GOO_GREEDY_STRATEGY_RESULT_SIZE;
 
 /*
  * Working state for a single GOO search invocation.
@@ -94,6 +95,7 @@ typedef struct GooCandidate
 {
 	RelOptInfo *left;			/* left input clump */
 	RelOptInfo *right;			/* right input clump */
+	double		result_size;	/* estimated result size in bytes */
 	Cost		total_cost;		/* total cost of cheapest path */
 }			GooCandidate;
 
@@ -129,6 +131,105 @@ goo_join_search(PlannerInfo *root, int levels_needed,
 	int			base_rel_count;
 	struct HTAB *base_hash;
 
+	/* If COMBINED mode, try both strategies and return the better one */
+	if (goo_greedy_strategy == GOO_GREEDY_STRATEGY_COMBINED)
+	{
+		RelOptInfo *result_cost;
+		RelOptInfo *result_size;
+		Cost		cost_result_cost;
+		Cost		size_result_cost;
+		int			saved_strategy;
+		List	   *saved_join_rel_list_cost;
+		struct HTAB *saved_join_rel_hash_cost;
+
+		/* Save the original strategy */
+		saved_strategy = goo_greedy_strategy;
+
+		/*
+		 * First try: COST strategy
+		 */
+		goo_greedy_strategy = GOO_GREEDY_STRATEGY_COST;
+		state = goo_init_state(root, initial_rels);
+		base_rel_count = list_length(root->join_rel_list);
+		base_hash = root->join_rel_hash;
+
+		result_cost = goo_search_internal(state);
+
+		if (result_cost == NULL)
+		{
+			root->join_rel_list = list_truncate(root->join_rel_list, base_rel_count);
+			root->join_rel_hash = base_hash;
+			elog(ERROR, "GOO join search (COST strategy) failed to find a valid join order");
+		}
+
+		cost_result_cost = result_cost->cheapest_total_path->total_cost;
+		goo_destroy_state(state);
+
+		/*
+		 * Save the COST strategy's join_rel_list and join_rel_hash. If COST
+		 * strategy wins, we'll restore these instead of re-running.
+		 */
+		saved_join_rel_list_cost = root->join_rel_list;
+		saved_join_rel_hash_cost = root->join_rel_hash;
+
+		/*
+		 * Second try: RESULT_SIZE strategy
+		 *
+		 * Reset the planner state to start fresh. We need to clear all
+		 * intermediate join relations created by the first search.
+		 */
+		root->join_rel_list = list_truncate(root->join_rel_list, base_rel_count);
+		root->join_rel_hash = base_hash;
+
+		goo_greedy_strategy = GOO_GREEDY_STRATEGY_RESULT_SIZE;
+		state = goo_init_state(root, initial_rels);
+
+		result_size = goo_search_internal(state);
+
+		if (result_size == NULL)
+		{
+			root->join_rel_list = list_truncate(root->join_rel_list, base_rel_count);
+			root->join_rel_hash = base_hash;
+			elog(ERROR, "GOO join search (RESULT_SIZE strategy) failed to find a valid join order");
+		}
+
+		size_result_cost = result_size->cheapest_total_path->total_cost;
+		goo_destroy_state(state);
+
+		/* Restore the original strategy */
+		goo_greedy_strategy = saved_strategy;
+
+		/*
+		 * Compare the two results and return the one with lower cost
+		 */
+		if (cost_result_cost <= size_result_cost)
+		{
+			/*
+			 * COST strategy won. Restore the COST strategy's join relations
+			 * instead of re-running the search.
+			 */
+			root->join_rel_list = saved_join_rel_list_cost;
+			root->join_rel_hash = saved_join_rel_hash_cost;
+
+			elog(DEBUG1, "GOO COMBINED mode: COST strategy chosen (cost: %.2f vs %.2f)",
+				 cost_result_cost, size_result_cost);
+
+			return result_cost;
+		}
+		else
+		{
+			/*
+			 * RESULT_SIZE strategy won. The join relations are already in
+			 * place, so we can return the result directly.
+			 */
+			elog(DEBUG1, "GOO COMBINED mode: RESULT_SIZE strategy chosen (cost: %.2f vs %.2f)",
+				 size_result_cost, cost_result_cost);
+
+			return result_size;
+		}
+	}
+
+	/* Normal single-strategy mode */
 	/* Initialize search state and memory contexts */
 	state = goo_init_state(root, initial_rels);
 
@@ -417,6 +518,8 @@ static GooCandidate * goo_build_candidate(GooState * state, RelOptInfo *left,
 	int			saved_rel_len;
 	struct HTAB *saved_hash;
 	RelOptInfo *joinrel;
+	double		join_rows;
+	double		result_size;
 	Cost		total_cost;
 	GooCandidate *cand;
 
@@ -477,6 +580,9 @@ static GooCandidate * goo_build_candidate(GooState * state, RelOptInfo *left,
 		set_cheapest(grouped_rel);
 	}
 
+	join_rows = joinrel->rows;
+
+	result_size = join_rows * joinrel->reltarget->width;
 	total_cost = joinrel->cheapest_total_path->total_cost;
 
 	/*
@@ -495,6 +601,7 @@ static GooCandidate * goo_build_candidate(GooState * state, RelOptInfo *left,
 	cand = palloc(sizeof(GooCandidate));
 	cand->left = left;
 	cand->right = right;
+	cand->result_size = result_size;
 	cand->total_cost = total_cost;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -608,5 +715,18 @@ goo_commit_join(GooState * state, GooCandidate * cand)
 static bool
 goo_candidate_better(GooCandidate * a, GooCandidate * b)
 {
-	return (a->total_cost < b->total_cost);
+	switch (goo_greedy_strategy)
+	{
+		case GOO_GREEDY_STRATEGY_COMBINED:
+			/* Should not be called in COMBINED mode */
+			elog(ERROR, "goo_candidate_better should not be called in COMBINED mode");
+			return false;
+
+		case GOO_GREEDY_STRATEGY_COST:
+			return a->total_cost < b->total_cost;
+
+		case GOO_GREEDY_STRATEGY_RESULT_SIZE:
+		default:
+			return a->result_size < b->result_size;
+	}
 }
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index a8ce31ab8a7..2a017195820 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1154,6 +1154,16 @@
   max => 'MAX_KILOBYTES',
 },
 
+/* WIP: only for testing */
+{ name => 'goo_greedy_strategy', type => 'enum', context => 'PGC_USERSET', group => 'QUERY_TUNING_GEQO',
+  short_desc => 'Selects the heuristic used by GOO to compare join candidates.',
+  long_desc => 'Valid values are cost, result_size, and combined.',
+  flags => 'GUC_EXPLAIN',
+  variable => 'goo_greedy_strategy',
+  boot_val => 'GOO_GREEDY_STRATEGY_RESULT_SIZE',
+  options => 'goo_greedy_strategy_options',
+},
+
 { name => 'gss_accept_delegation', type => 'bool', context => 'PGC_SIGHUP', group => 'CONN_AUTH_AUTH',
   short_desc => 'Sets whether GSSAPI delegation should be accepted from the client.',
   variable => 'pg_gss_accept_delegation',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..11b299453fa 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -411,6 +411,13 @@ static const struct config_enum_entry plan_cache_mode_options[] = {
 	{NULL, 0, false}
 };
 
+static const struct config_enum_entry goo_greedy_strategy_options[] = {
+	{"cost", GOO_GREEDY_STRATEGY_COST, false},
+	{"result_size", GOO_GREEDY_STRATEGY_RESULT_SIZE, false},
+	{"combined", GOO_GREEDY_STRATEGY_COMBINED, false},
+	{NULL, 0, false}
+};
+
 static const struct config_enum_entry password_encryption_options[] = {
 	{"md5", PASSWORD_TYPE_MD5, false},
 	{"scram-sha-256", PASSWORD_TYPE_SCRAM_SHA_256, false},
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 5b3ebe5f1d2..c42610b34b3 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -16,12 +16,19 @@
 
 #include "nodes/pathnodes.h"
 
+typedef enum GooGreedyStrategy
+{
+	GOO_GREEDY_STRATEGY_COST,
+	GOO_GREEDY_STRATEGY_RESULT_SIZE,
+	GOO_GREEDY_STRATEGY_COMBINED
+}			GooGreedyStrategy;
 
 /*
  * allpaths.c
  */
 extern PGDLLIMPORT bool enable_geqo;
 extern PGDLLIMPORT bool enable_goo_join_search;
+extern PGDLLIMPORT int goo_greedy_strategy;
 extern PGDLLIMPORT bool enable_eager_aggregate;
 extern PGDLLIMPORT int geqo_threshold;
 extern PGDLLIMPORT double min_eager_agg_group_size;
-- 
2.39.3 (Apple Git-146)

