Store information about range-table flattening in the final plan.
authorRobert Haas <rhaas@postgresql.org>
Mon, 20 Oct 2025 16:00:18 +0000 (12:00 -0400)
committerRobert Haas <rhaas@postgresql.org>
Thu, 6 Nov 2025 16:41:58 +0000 (11:41 -0500)
Suppose that we're currently planning a query and, when that same
query was previously planned and executed, we learned something about
how a certain table within that query should be planned. We want to
take note when that same table is being planned during the current
planning cycle, but this is difficult to do, because the RTI of the
table from the previous plan won't necessarily be equal to the RTI
that we see during the current planning cycle. This is because each
subquery has a separate range table during planning, but these are
flattened into one range table when constructing the final plan,
changing RTIs.

Commit 8c49a484e8ebb0199fba4bd68eaaedaf49b48ed0 allows us to match up
subqueries seen in the previous planning cycles with the subqueries
currently being planned just by comparing textual names, but that's
not quite enough to let us deduce anything about individual tables,
because we don't know where each subquery's range table appears in
the final, flattened range table.

To fix that, store a list of SubPlanRTInfo objects in the final
planned statement, each including the name of the subplan, the offset
at which it begins in the flattened range table, and whether or not
it was a dummy subplan -- if it was, some RTIs may have been dropped
from the final range table, but also there's no need to control how
a dummy subquery gets planned. The toplevel subquery has no name and
always begins at rtoffset 0, so we make no entry for it.

This commit teaches pg_overexplain'e RANGE_TABLE option to make use
of this new data to display the subquery name for each range table
entry.

NOTE TO REVIEWERS: If there's a clean way to make pg_overexplain display
this information without the new infrastructure provided by this patch,
then this patch is unnecessary. I thought there would be a way to do
that, but I couldn't figure anything out: there seems to be nothing that
records in the final PlannedStmt where subquery's range table ends and
the next one begins. In practice, one could usually figure it out by
matching up tables by relation OID, but that's neither clean nor
theoretically sound.

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 c4fd646b999c1d14a4513079789b6ea3800ea9ee..0e6b3f60f318f5f85212a8d0db5bf60e5b1b1332 100644 (file)
@@ -607,6 +607,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->appendRelations = glob->appendRelations;
        result->subplans = glob->subplans;
index ccdc9bc264ab93ed4fc06b6c853362c584d89d42..adabae09a233ce9493035805be2073e3da3ad2ea 100644 (file)
@@ -399,6 +399,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 30d889b54c53c7da707db19c384a7ebed9fdcacf..a3a800869df11b07bc26184a0ae3040f7333236a 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 c4393a9432116e72ee8ebb098cc0f85752cc9319..1526dd2ec6bdb2c276d2a715c7c2abe13bf41130 100644 (file)
@@ -131,6 +131,9 @@ typedef struct PlannedStmt
         */
        List       *subplans;
 
+       /* a list of SubPlanRTInfo objects */
+       List       *subrtinfos;
+
        /* indices of subplans that require REWIND */
        Bitmapset  *rewindPlanIDs;
 
@@ -1821,4 +1824,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;
+       const char *plan_name;
+       Index           rtoffset;
+       bool            dummy;
+} SubPlanRTInfo;
+
 #endif                                                 /* PLANNODES_H */
index 432509277c98118a30cb624fae0340f5b9b07fb0..60aa0b0937b5704dfbc79ad7588fb859f873f595 100644 (file)
@@ -2889,6 +2889,7 @@ SubLink
 SubLinkType
 SubOpts
 SubPlan
+SubPlanRTInfo
 SubPlanState
 SubRelInfo
 SubRemoveRels