add 'generate_advice_string' to pps, so we always know during planning
authorRobert Haas <[email protected]>
Mon, 1 Dec 2025 17:15:15 +0000 (12:15 -0500)
committerRobert Haas <[email protected]>
Thu, 11 Dec 2025 15:25:27 +0000 (10:25 -0500)
whether we're doing this thing

contrib/pg_plan_advice/pg_plan_advice.c
contrib/pg_plan_advice/pg_plan_advice.h
contrib/pg_plan_advice/pgpa_planner.c
contrib/pg_plan_advice/pgpa_walker.c

index f32e8b7a0d3bb121fe22d244281276b3462a88a3..865931c960f2492090ef08f3da39d0f895c1ec11 100644 (file)
@@ -217,6 +217,19 @@ pg_plan_advice_dsa_area(void)
        return pgpa_dsa_area;
 }
 
+/*
+ * Was the PLAN_ADVICE option specified and not set to false?
+ */
+bool
+pg_plan_advice_should_explain(ExplainState *es)
+{
+       bool       *plan_advice = NULL;
+
+       if (es != NULL)
+               plan_advice = GetExplainExtensionState(es, es_extension_id);
+       return plan_advice != NULL && *plan_advice;
+}
+
 /*
  * Handler for EXPLAIN (PLAN_ADVICE).
  */
@@ -329,7 +342,7 @@ pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
                                                                         ParamListInfo params,
                                                                         QueryEnvironment *queryEnv)
 {
-       bool       *plan_advice = GetExplainExtensionState(es, es_extension_id);
+       bool            should_explain;
        DefElem    *pgpa_item;
        List       *pgpa_list;
 
@@ -337,6 +350,9 @@ pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
                prev_explain_per_plan(plannedstmt, into, es, queryString, params,
                                                          queryEnv);
 
+       /* Should an advice string be part of the EXPLAIN output? */
+       should_explain = pg_plan_advice_should_explain(es);
+
        /* Find any data pgpa_planner_shutdown stashed in the PlannedStmt. */
        pgpa_item = find_defelem_by_defname(plannedstmt->extension_state,
                                                                                "pg_plan_advice");
@@ -354,7 +370,7 @@ pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
         * recorded, and therefore this won't be able to show anything.
         */
        if (pgpa_list != NULL && (pg_plan_advice_always_explain_supplied_advice ||
-                                                         (plan_advice != NULL && *plan_advice)))
+                                                         should_explain))
        {
                DefElem    *feedback;
 
@@ -367,10 +383,10 @@ pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
         * If the PLAN_ADVICE option was specified -- and not sent to FALSE --
         * show generated advice.
         */
-       if (plan_advice != NULL && *plan_advice)
+       if (should_explain)
        {
                DefElem    *advice_string_item;
-               char       *advice_string;
+               char       *advice_string = NULL;
 
                advice_string_item =
                        find_defelem_by_defname(pgpa_list, "advice_string");
@@ -379,22 +395,7 @@ pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
                        /* Advice has already been generated; we can reuse it. */
                        advice_string = strVal(advice_string_item->arg);
                }
-               else
-               {
-                       pgpa_plan_walker_context walker;
-                       StringInfoData buf;
-                       pgpa_identifier *rt_identifiers;
-
-                       /* Advice not yet generated; do that now. */
-                       pgpa_plan_walker(&walker, plannedstmt);
-                       rt_identifiers =
-                               pgpa_create_identifiers_for_planned_stmt(plannedstmt);
-                       initStringInfo(&buf);
-                       pgpa_output_advice(&buf, &walker, rt_identifiers);
-                       advice_string = buf.data;
-               }
-
-               if (advice_string[0] != '\0')
+               if (advice_string != NULL && advice_string[0] != '\0')
                        pg_plan_advice_explain_text_multiline(es, "Generated Plan Advice",
                                                                                                  advice_string);
        }
index 86efb3b61139c02b785511a3b36ddd4e42afd1e8..02e65a3382b0c532e04c6f2d90d7a8082ea959e5 100644 (file)
@@ -12,6 +12,7 @@
 #ifndef PG_PLAN_ADVICE_H
 #define PG_PLAN_ADVICE_H
 
+#include "commands/explain_state.h"
 #include "nodes/plannodes.h"
 #include "storage/lwlock.h"
 #include "utils/dsa.h"
@@ -33,5 +34,6 @@ extern char *pg_plan_advice_advice;
 extern MemoryContext pg_plan_advice_get_mcxt(void);
 extern pgpa_shared_state *pg_plan_advice_attach(void);
 extern dsa_area *pg_plan_advice_dsa_area(void);
+extern bool pg_plan_advice_should_explain(ExplainState *es);
 
 #endif
index 11c8065d15e47cdc0a0a12e734e0a0d698d99755..c75bb779cbbb0d082049d9be5334636adcc71b8c 100644 (file)
@@ -79,6 +79,7 @@ pgpa_ri_checker_compare_key(pgpa_ri_checker_key a, pgpa_ri_checker_key b)
 typedef struct pgpa_planner_state
 {
        ExplainState *explain_state;
+       bool            generate_advice_string;
        pgpa_trove *trove;
        MemoryContext trove_cxt;
 
@@ -441,7 +442,7 @@ pgpa_join_path_setup(PlannerInfo *root, RelOptInfo *joinrel,
 }
 
 /*
- * Prepare advice for use by a query.
+ * Carry out whatever setup work we need to do before planning.
  */
 static void
 pgpa_planner_setup(PlannerGlobal *glob, Query *parse, const char *query_string,
@@ -449,8 +450,20 @@ pgpa_planner_setup(PlannerGlobal *glob, Query *parse, const char *query_string,
 {
        pgpa_trove *trove = NULL;
        pgpa_planner_state *pps;
+       bool            generate_advice_string = false;
        bool            needs_pps = false;
 
+       /*
+        * Decide whether we need to generate an advice string. We must do this if
+        * at least one collector is enabled or if the user has requested it using
+        * the EXPLAIN (PLAN_ADVICE) option.
+        */
+       generate_advice_string = (pg_plan_advice_local_collection_limit > 0 ||
+                                                         pg_plan_advice_shared_collection_limit > 0 ||
+                                                         pg_plan_advice_should_explain(es));
+       if (generate_advice_string)
+               needs_pps = true;
+
        /*
         * If any advice was provided, build a trove of advice for use during
         * planning.
@@ -489,11 +502,17 @@ pgpa_planner_setup(PlannerGlobal *glob, Query *parse, const char *query_string,
        needs_pps = true;
 #endif
 
-       /* Initialize and store private state, if required. */
+       /*
+        * We only create and initialize a private state object if it's needed for
+        * some purpose. That could be (1) recording that we will need to generate
+        * an advice string, (2) storing a trove of supplied advice, or (3)
+        * facilitating debugging cross-checks when asserts are enabled.
+        */
        if (needs_pps)
        {
                pps = palloc0_object(pgpa_planner_state);
                pps->explain_state = es;
+               pps->generate_advice_string = generate_advice_string;
                pps->trove = trove;
 #ifdef USE_ASSERT_CHECKING
                pps->ri_check_hash =
@@ -515,7 +534,7 @@ pgpa_planner_shutdown(PlannerGlobal *glob, Query *parse,
        ExplainState *es = NULL;
        pgpa_plan_walker_context walker = {0};  /* placate compiler */
        bool            do_advice_feedback;
-       bool            do_collect_advice;
+       bool            generate_advice_string = false;
        List       *pgpa_items = NIL;
        pgpa_identifier *rt_identifiers = NULL;
 
@@ -525,27 +544,26 @@ pgpa_planner_shutdown(PlannerGlobal *glob, Query *parse,
        {
                trove = pps->trove;
                es = pps->explain_state;
+               generate_advice_string = pps->generate_advice_string;
        }
 
-       /* If at least one collector is enabled, generate advice. */
-       do_collect_advice = (pg_plan_advice_local_collection_limit > 0 ||
-                                                pg_plan_advice_shared_collection_limit > 0);
-
-       /* If we applied advice, generate feedback. */
+       /*
+        * If any advice was specified, and if we're running under EXPLAIN, then
+        * we should try try to provide advice feedback.
+        *
+        * If we're trying to generate an advice string or if we're trying to
+        * provide advice feedback, then we will need to create range table
+        * identifiers.
+        */
        do_advice_feedback = (trove != NULL && es != NULL);
-
-       /* If either of the above apply, analyze the resulting PlannedStmt. */
-       if (do_collect_advice || do_advice_feedback)
+       if (generate_advice_string || do_advice_feedback)
        {
                pgpa_plan_walker(&walker, pstmt);
                rt_identifiers = pgpa_create_identifiers_for_planned_stmt(pstmt);
        }
 
-       /*
-        * If advice collection is enabled, put the advice in string form and send
-        * it to the collector.
-        */
-       if (do_collect_advice)
+       /* Generate the advice string, if we need to do so. */
+       if (generate_advice_string)
        {
                char       *advice_string;
                StringInfoData buf;
index 4ed222910963f2bad85b14bb7290b9b9eb026818..db8cc4352c33dd5d61e3d4fb1ccc9ee2a4f14ee6 100644 (file)
@@ -700,7 +700,7 @@ pgpa_walker_join_order_matches(pgpa_unrolled_join *ujoin,
                                                           pgpa_advice_target *target,
                                                           bool toplevel)
 {
-       int             nchildren = list_length(target->children);
+       int                     nchildren = list_length(target->children);
 
        Assert(target->ttype == PGPA_TARGET_ORDERED_LIST);