Store information about range-table flattening in the final plan.
authorRobert Haas <rhaas@postgresql.org>
Fri, 21 Mar 2025 15:06:35 +0000 (11:06 -0400)
committerRobert Haas <rhaas@postgresql.org>
Mon, 19 May 2025 15:02:50 +0000 (11:02 -0400)
During planning, there is one range table per subquery; at the end if
planning, those separate range tables are flattened into a single
range table. Prior to this change, it was impractical for code
examining the final plan to understand which parts of the flattened
range table came from which subquery's range table.

If the only consumer of the final plan is the executor, that is
completely fine. However, if some code wants to examine the final
plan, or what happens when we execute it, and extract information from
it that be used in future planning cycles, it's inconvenient.  So,
this commit remembers in the final plan which part of the final range
table came from which subquery's range table.

Additionally, this commit teaches pg_overexplain'e RANGE_TABLE option
to display the subquery name for each range table entry.

contrib/pg_overexplain/pg_overexplain.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/include/nodes/pathnodes.h
src/include/nodes/plannodes.h
src/tools/pgindent/typedefs.list

index bd70b6d9d5ecc33c11f70b2e54bfaea106773460..5dc707d69e32ca4e7343f3c9f543c691f5a6c8e0 100644 (file)
@@ -395,6 +395,8 @@ static void
 overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
 {
        Index           rti;
+       ListCell   *lc_subrtinfo = list_head(plannedstmt->subrtinfos);
+       SubPlanRTInfo *rtinfo = NULL;
 
        /* Open group, one entry per RangeTblEntry */
        ExplainOpenGroup("Range Table", "Range Table", false, es);
@@ -405,6 +407,18 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
                RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable);
                char       *kind = NULL;
                char       *relkind;
+               SubPlanRTInfo *next_rtinfo;
+
+               /* Advance to next SubRTInfo, if it's time. */
+               if (lc_subrtinfo != NULL)
+               {
+                       next_rtinfo = lfirst(lc_subrtinfo);
+                       if (rti > next_rtinfo->rtoffset)
+                       {
+                               rtinfo = next_rtinfo;
+                               lc_subrtinfo = lnext(plannedstmt->subrtinfos, lc_subrtinfo);
+                       }
+               }
 
                /* NULL entries are possible; skip them */
                if (rte == NULL)
@@ -469,6 +483,28 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
                        ExplainPropertyBool("In From Clause", rte->inFromCl, es);
                }
 
+               /*
+                * Indicate which subplan is the origin of which RTE. Note dummy
+                * subplans. Here again, we crunch more onto one line in text format.
+                */
+               if (rtinfo != NULL)
+               {
+                       if (es->format == EXPLAIN_FORMAT_TEXT)
+                       {
+                               if (!rtinfo->dummy)
+                                       ExplainPropertyText("Subplan", rtinfo->plan_name, es);
+                               else
+                                       ExplainPropertyText("Subplan",
+                                                                               psprintf("%s (dummy)",
+                                                                                                rtinfo->plan_name), es);
+                       }
+                       else
+                       {
+                               ExplainPropertyText("Subplan", rtinfo->plan_name, es);
+                               ExplainPropertyBool("Subplan Is Dummy", rtinfo->dummy, es);
+                       }
+               }
+
                /* rte->alias is optional; rte->eref is requested */
                if (rte->alias != NULL)
                        overexplain_alias("Alias", rte->alias, es);
index 215491ccfd4abfd252f8b10e216e2b9bf20b793c..5dcd09e712a756184169b2eeb27a575366429a1b 100644 (file)
@@ -571,6 +571,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
        result->unprunableRelids = bms_difference(glob->allRelids,
                                                                                          glob->prunableRelids);
        result->permInfos = glob->finalrteperminfos;
+       result->subrtinfos = glob->subrtinfos;
        result->resultRelations = glob->resultRelations;
        result->firstResultRels = glob->firstResultRels;
        result->appendRelations = glob->appendRelations;
index 7f241cddb4c2c35ab325c938c104cc15c4fa3c74..6f0d97f3936a28d911a67ce68031d17cadb905d4 100644 (file)
@@ -395,6 +395,26 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
        Index           rti;
        ListCell   *lc;
 
+       /*
+        * Record enough information to make it possible for code that looks at
+        * the final range table to understand how it was constructed. (If
+        * finalrtable is still NIL, then this is the very topmost PlannerInfo,
+        * which will always have plan_name == NULL and rtoffset == 0; we omit the
+        * degenerate list entry.)
+        */
+       if (root->glob->finalrtable != NIL)
+       {
+               SubPlanRTInfo *rtinfo = makeNode(SubPlanRTInfo);
+
+               rtinfo->plan_name = root->plan_name;
+               rtinfo->rtoffset = list_length(root->glob->finalrtable);
+
+               /* When recursing = true, it's an unplanned or dummy subquery. */
+               rtinfo->dummy = recursing;
+
+               root->glob->subrtinfos = lappend(root->glob->subrtinfos, rtinfo);
+       }
+
        /*
         * Add the query's own RTEs to the flattened rangetable.
         *
index e009e131bc3b4b14c18c6720cbe25746da845fd9..61ba04d014f9d92aa971d92672597de543aaecde 100644 (file)
@@ -135,6 +135,9 @@ typedef struct PlannerGlobal
        /* "flat" list of RTEPermissionInfos */
        List       *finalrteperminfos;
 
+       /* list of SubPlanRTInfo nodes */
+       List       *subrtinfos;
+
        /* "flat" list of PlanRowMarks */
        List       *finalrowmarks;
 
index 782fb471b66462016475e1ec3bc20fd93dab81c9..9df11cd394a0fa74ef8d55f47a316cb6e9c43008 100644 (file)
@@ -120,6 +120,9 @@ typedef struct PlannedStmt
         */
        List       *subplans;
 
+       /* a list of SubPlanRTInfo objects */
+       List       *subrtinfos;
+
        /* indices of subplans that require REWIND */
        Bitmapset  *rewindPlanIDs;
 
@@ -1780,4 +1783,18 @@ typedef enum MonotonicFunction
        MONOTONICFUNC_BOTH = MONOTONICFUNC_INCREASING | MONOTONICFUNC_DECREASING,
 } MonotonicFunction;
 
+/*
+ * SubPlanRTInfo
+ *
+ * Information about which range table entries came from which subquery
+ * planning cycles.
+ */
+typedef struct SubPlanRTInfo
+{
+       NodeTag         type;
+       char       *plan_name;
+       Index           rtoffset;
+       bool            dummy;
+} SubPlanRTInfo;
+
 #endif                                                 /* PLANNODES_H */
index 9ea573fae210ec067c9a9d463b3be193979346e0..1c6a7252ee49527ff2b4559a17e343f8ce6b2d4c 100644 (file)
@@ -4309,3 +4309,4 @@ zic_t
 ExplainExtensionOption
 ExplainOptionHandler
 overexplain_options
+SubPlanRTInfo