From 641cf91bf28d2f449f2b6b87d75bfe5cd27298f3 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 25 Aug 2025 15:22:57 -0400
Subject: [PATCH v3 5/5] not for commit: count distinct joinrels and joinrel
 planning attempts

---
 .../expected/pg_overexplain.out               |  22 +++-
 contrib/pg_overexplain/pg_overexplain.c       | 107 ++++++++++++++++++
 2 files changed, 124 insertions(+), 5 deletions(-)

diff --git a/contrib/pg_overexplain/expected/pg_overexplain.out b/contrib/pg_overexplain/expected/pg_overexplain.out
index 6de02323d7c..d8d3e36d7f1 100644
--- a/contrib/pg_overexplain/expected/pg_overexplain.out
+++ b/contrib/pg_overexplain/expected/pg_overexplain.out
@@ -38,7 +38,9 @@ EXPLAIN (DEBUG) SELECT 1;
    Relation OIDs: none
    Executor Parameter Types: none
    Parse Location: 0 to end
-(11 rows)
+   Total Joinrel Attempts: 0
+   Distinct Joinrels: 0
+(13 rows)
 
 EXPLAIN (RANGE_TABLE) SELECT 1;
                 QUERY PLAN                
@@ -120,6 +122,8 @@ $$);
    Relation OIDs: NNN...
    Executor Parameter Types: none
    Parse Location: 0 to end
+   Total Joinrel Attempts: 0
+   Distinct Joinrels: 0
  RTI 1 (relation, inherited, in-from-clause):
    Eref: vegetables (id, name, genus)
    Relation: vegetables
@@ -141,7 +145,7 @@ $$);
    Relation Kind: relation
    Relation Lock Mode: AccessShareLock
  Unprunable RTIs: 1 3 4
-(53 rows)
+(55 rows)
 
 -- Test a different output format.
 SELECT explain_filter($$
@@ -241,6 +245,8 @@ $$);
        <Relation-OIDs>NNN...</Relation-OIDs>                        +
        <Executor-Parameter-Types>none</Executor-Parameter-Types>    +
        <Parse-Location>0 to end</Parse-Location>                    +
+       <Total-Joinrel-Attempts>0</Total-Joinrel-Attempts>           +
+       <Distinct-Joinrels>0</Distinct-Joinrels>                     +
      </PlannedStmt>                                                 +
      <Range-Table>                                                  +
        <Range-Table-Entry>                                          +
@@ -345,7 +351,9 @@ $$);
    Relation OIDs: NNN...
    Executor Parameter Types: none
    Parse Location: 0 to end
-(37 rows)
+   Total Joinrel Attempts: 0
+   Distinct Joinrels: 0
+(39 rows)
 
 SET debug_parallel_query = false;
 RESET enable_seqscan;
@@ -373,7 +381,9 @@ $$);
    Relation OIDs: NNN...
    Executor Parameter Types: 0
    Parse Location: 0 to end
-(15 rows)
+   Total Joinrel Attempts: 0
+   Distinct Joinrels: 0
+(17 rows)
 
 -- Create an index, and then attempt to force a nested loop with inner index
 -- scan so that we can see parameter-related information. Also, let's try
@@ -437,7 +447,9 @@ $$);
    Relation OIDs: NNN...
    Executor Parameter Types: 23
    Parse Location: 0 to end
-(47 rows)
+   Total Joinrel Attempts: 2
+   Distinct Joinrels: 1
+(49 rows)
 
 RESET enable_hashjoin;
 RESET enable_material;
diff --git a/contrib/pg_overexplain/pg_overexplain.c b/contrib/pg_overexplain/pg_overexplain.c
index de824566f8c..bf4852fcadc 100644
--- a/contrib/pg_overexplain/pg_overexplain.c
+++ b/contrib/pg_overexplain/pg_overexplain.c
@@ -16,6 +16,10 @@
 #include "commands/explain_format.h"
 #include "commands/explain_state.h"
 #include "fmgr.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/extendplan.h"
+#include "optimizer/paths.h"
+#include "optimizer/planner.h"
 #include "parser/parsetree.h"
 #include "storage/lock.h"
 #include "utils/builtins.h"
@@ -32,6 +36,12 @@ typedef struct
 	bool		range_table;
 } overexplain_options;
 
+typedef struct
+{
+	int			total_joinrel_attempts;
+	int			distinct_joinrel_count;
+} overexplain_plannerglobal;
+
 static overexplain_options *overexplain_ensure_options(ExplainState *es);
 static void overexplain_debug_handler(ExplainState *es, DefElem *opt,
 									  ParseState *pstate);
@@ -57,9 +67,27 @@ static void overexplain_bitmapset(const char *qlabel, Bitmapset *bms,
 static void overexplain_intlist(const char *qlabel, List *list,
 								ExplainState *es);
 
+static void overexplain_planner_setup_hook(PlannerGlobal *glob, Query *parse,
+										   const char *query_string,
+										   double *tuple_fraction);
+static void overexplain_planner_shutdown_hook(PlannerGlobal *glob,
+											  Query *parse,
+											  const char *query_string,
+											  PlannedStmt *pstmt);
+static void overexplain_set_join_pathlist_hook(PlannerInfo *root,
+											   RelOptInfo *joinrel,
+											   RelOptInfo *outerrel,
+											   RelOptInfo *innerrel,
+											   JoinType jointype,
+											   JoinPathExtraData *extra);
+
 static int	es_extension_id;
+static int	planner_extension_id = -1;
 static explain_per_node_hook_type prev_explain_per_node_hook;
 static explain_per_plan_hook_type prev_explain_per_plan_hook;
+static planner_setup_hook_type prev_planner_setup_hook;
+static planner_shutdown_hook_type prev_planner_shutdown_hook;
+static set_join_pathlist_hook_type prev_set_join_pathlist_hook;
 
 /*
  * Initialization we do when this module is loaded.
@@ -70,6 +98,9 @@ _PG_init(void)
 	/* Get an ID that we can use to cache data in an ExplainState. */
 	es_extension_id = GetExplainExtensionId("pg_overexplain");
 
+	/* Get an ID that we can use to cache data in the planner. */
+	planner_extension_id = GetPlannerExtensionId("pg_overexplain");
+
 	/* Register the new EXPLAIN options implemented by this module. */
 	RegisterExtensionExplainOption("debug", overexplain_debug_handler);
 	RegisterExtensionExplainOption("range_table",
@@ -80,6 +111,16 @@ _PG_init(void)
 	explain_per_node_hook = overexplain_per_node_hook;
 	prev_explain_per_plan_hook = explain_per_plan_hook;
 	explain_per_plan_hook = overexplain_per_plan_hook;
+
+	/* Example of planner_setup_hook/planner_shutdown_hook use */
+	prev_planner_setup_hook = planner_setup_hook;
+	planner_setup_hook = overexplain_planner_setup_hook;
+	prev_planner_shutdown_hook = planner_shutdown_hook;
+	planner_shutdown_hook = overexplain_planner_shutdown_hook;
+
+	/* Support for above example */
+	prev_set_join_pathlist_hook = set_join_pathlist_hook;
+	set_join_pathlist_hook = overexplain_set_join_pathlist_hook;
 }
 
 /*
@@ -369,6 +410,29 @@ overexplain_debug(PlannedStmt *plannedstmt, ExplainState *es)
 									 plannedstmt->stmt_len),
 							es);
 
+	{
+		DefElem    *elem = NULL;
+
+		foreach_node(DefElem, de, plannedstmt->extension_state)
+		{
+			if (strcmp(de->defname, "pg_overexplain") == 0)
+			{
+				elem = de;
+				break;
+			}
+		}
+
+		if (elem != NULL)
+		{
+			List	   *l = castNode(List, elem->arg);
+
+			ExplainPropertyInteger("Total Joinrel Attempts", NULL,
+								   intVal(linitial(l)), es);
+			ExplainPropertyInteger("Distinct Joinrels", NULL,
+								   intVal(lsecond(l)), es);
+		}
+	}
+
 	/* Done with this group. */
 	if (es->format == EXPLAIN_FORMAT_TEXT)
 		es->indent--;
@@ -772,3 +836,46 @@ overexplain_intlist(const char *qlabel, List *list, ExplainState *es)
 
 	pfree(buf.data);
 }
+
+static void
+overexplain_planner_setup_hook(PlannerGlobal *glob, Query *parse,
+							   const char *query_string,
+							   double *tuple_fraction)
+{
+	overexplain_plannerglobal *g = palloc0_object(overexplain_plannerglobal);
+
+	SetPlannerGlobalExtensionState(glob, planner_extension_id, g);
+}
+
+static void
+overexplain_planner_shutdown_hook(PlannerGlobal *glob, Query *parse,
+								  const char *query_string, PlannedStmt *pstmt)
+{
+	overexplain_plannerglobal *g;
+	DefElem    *elem;
+	List	   *l;
+
+	g = GetPlannerGlobalExtensionState(glob, planner_extension_id);
+	l = list_make2(makeInteger(g->total_joinrel_attempts),
+				   makeInteger(g->distinct_joinrel_count));
+	elem = makeDefElem("pg_overexplain", (Node *) l, -1);
+	pstmt->extension_state = lappend(pstmt->extension_state, elem);
+}
+
+static void
+overexplain_set_join_pathlist_hook(PlannerInfo *root, RelOptInfo *joinrel,
+								   RelOptInfo *outerrel, RelOptInfo *innerrel,
+								   JoinType jointype, JoinPathExtraData *extra)
+{
+	overexplain_plannerglobal *g;
+
+	g = GetPlannerGlobalExtensionState(root->glob, planner_extension_id);
+	g->total_joinrel_attempts++;
+
+	if (GetRelOptInfoExtensionState(joinrel, planner_extension_id) == NULL)
+	{
+		g->distinct_joinrel_count++;
+		/* set any non-NULL value to avoid double-counting */
+		SetRelOptInfoExtensionState(joinrel, planner_extension_id, g);
+	}
+}
-- 
2.39.5 (Apple Git-154)

