]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Store information about range-table flattening in the final plan.
authorRobert Haas <rhaas@postgresql.org>
Tue, 10 Feb 2026 20:33:39 +0000 (15:33 -0500)
committerRobert Haas <rhaas@postgresql.org>
Tue, 10 Feb 2026 20:33:39 +0000 (15:33 -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's RANGE_TABLE option to make use
of this new data to display the subquery name for each range table
entry.

Reviewed-by: Lukas Fittl <lukas@fittl.com>
Reviewed-by: Jakub Wartak <jakub.wartak@enterprisedb.com>
Reviewed-by: Greg Burd <greg@burd.me>
Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com>
Reviewed-by: Amit Langote <amitlangote09@gmail.com>
Reviewed-by: Haibo Yan <tristan.yim@gmail.com>
Reviewed-by: Alexandra Wang <alexandra.wang.oss@gmail.com>
Discussion: http://postgr.es/m/CA+TgmoZ-Jh1T6QyWoCODMVQdhTUPYkaZjWztzP1En4=ZHoKPzw@mail.gmail.com

contrib/pg_overexplain/expected/pg_overexplain.out
contrib/pg_overexplain/pg_overexplain.c
contrib/pg_overexplain/sql/pg_overexplain.sql
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 55d34666d87f84e76ffa8bb417ef82a45cfd82f2..f4ce828bc62a40a8abb43b1af85fae7f9c615b26 100644 (file)
@@ -489,3 +489,112 @@ INSERT INTO vegetables (name, genus) VALUES ('broccoflower', 'brassica');
  Result RTIs: 1
 (15 rows)
 
+-- should show "Subplan: sub"
+EXPLAIN (RANGE_TABLE, COSTS OFF)
+SELECT * FROM vegetables v,
+       (SELECT * FROM vegetables WHERE genus = 'daucus' OFFSET 0) sub;
+                  QUERY PLAN                  
+----------------------------------------------
+ Nested Loop
+   ->  Seq Scan on daucus vegetables
+         Filter: (genus = 'daucus'::text)
+         Scan RTI: 6
+   ->  Append
+         Append RTIs: 1
+         ->  Seq Scan on brassica v_1
+               Scan RTI: 3
+         ->  Seq Scan on daucus v_2
+               Scan RTI: 4
+ RTI 1 (relation, inherited, in-from-clause):
+   Alias: v ()
+   Eref: v (id, name, genus)
+   Relation: vegetables
+   Relation Kind: partitioned_table
+   Relation Lock Mode: AccessShareLock
+   Permission Info Index: 1
+ RTI 2 (subquery, in-from-clause):
+   Alias: sub ()
+   Eref: sub (id, name, genus)
+ RTI 3 (relation, in-from-clause):
+   Alias: v (id, name, genus)
+   Eref: v (id, name, genus)
+   Relation: brassica
+   Relation Kind: relation
+   Relation Lock Mode: AccessShareLock
+ RTI 4 (relation, in-from-clause):
+   Alias: v (id, name, genus)
+   Eref: v (id, name, genus)
+   Relation: daucus
+   Relation Kind: relation
+   Relation Lock Mode: AccessShareLock
+ RTI 5 (relation, inherited, in-from-clause):
+   Subplan: sub
+   Eref: vegetables (id, name, genus)
+   Relation: vegetables
+   Relation Kind: partitioned_table
+   Relation Lock Mode: AccessShareLock
+   Permission Info Index: 2
+ RTI 6 (relation, in-from-clause):
+   Subplan: sub
+   Alias: vegetables (id, name, genus)
+   Eref: vegetables (id, name, genus)
+   Relation: daucus
+   Relation Kind: relation
+   Relation Lock Mode: AccessShareLock
+ Unprunable RTIs: 1 3 4 5 6
+(47 rows)
+
+-- should show "Subplan: unnamed_subquery"
+EXPLAIN (RANGE_TABLE, COSTS OFF)
+SELECT * FROM vegetables v,
+       (SELECT * FROM vegetables WHERE genus = 'daucus' OFFSET 0);
+                  QUERY PLAN                  
+----------------------------------------------
+ Nested Loop
+   ->  Seq Scan on daucus vegetables
+         Filter: (genus = 'daucus'::text)
+         Scan RTI: 6
+   ->  Append
+         Append RTIs: 1
+         ->  Seq Scan on brassica v_1
+               Scan RTI: 3
+         ->  Seq Scan on daucus v_2
+               Scan RTI: 4
+ RTI 1 (relation, inherited, in-from-clause):
+   Alias: v ()
+   Eref: v (id, name, genus)
+   Relation: vegetables
+   Relation Kind: partitioned_table
+   Relation Lock Mode: AccessShareLock
+   Permission Info Index: 1
+ RTI 2 (subquery, in-from-clause):
+   Eref: unnamed_subquery (id, name, genus)
+ RTI 3 (relation, in-from-clause):
+   Alias: v (id, name, genus)
+   Eref: v (id, name, genus)
+   Relation: brassica
+   Relation Kind: relation
+   Relation Lock Mode: AccessShareLock
+ RTI 4 (relation, in-from-clause):
+   Alias: v (id, name, genus)
+   Eref: v (id, name, genus)
+   Relation: daucus
+   Relation Kind: relation
+   Relation Lock Mode: AccessShareLock
+ RTI 5 (relation, inherited, in-from-clause):
+   Subplan: unnamed_subquery
+   Eref: vegetables (id, name, genus)
+   Relation: vegetables
+   Relation Kind: partitioned_table
+   Relation Lock Mode: AccessShareLock
+   Permission Info Index: 2
+ RTI 6 (relation, in-from-clause):
+   Subplan: unnamed_subquery
+   Alias: vegetables (id, name, genus)
+   Eref: vegetables (id, name, genus)
+   Relation: daucus
+   Relation Kind: relation
+   Relation Lock Mode: AccessShareLock
+ Unprunable RTIs: 1 3 4 5 6
+(46 rows)
+
index 316ffd1c87f480b26991a31db877b8bbcf3f6b1b..bf8c768ed47aad6ca3fd86abbbb546d5e8c93860 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 42e275ac2f90698cb4ac0cf067da53a07fadb919..34a957cbed312cd73294119be6b45443ececf8e7 100644 (file)
@@ -110,3 +110,13 @@ SELECT * FROM vegetables WHERE genus = 'daucus';
 -- Also test a case that involves a write.
 EXPLAIN (RANGE_TABLE, COSTS OFF)
 INSERT INTO vegetables (name, genus) VALUES ('broccoflower', 'brassica');
+
+-- should show "Subplan: sub"
+EXPLAIN (RANGE_TABLE, COSTS OFF)
+SELECT * FROM vegetables v,
+       (SELECT * FROM vegetables WHERE genus = 'daucus' OFFSET 0) sub;
+
+-- should show "Subplan: unnamed_subquery"
+EXPLAIN (RANGE_TABLE, COSTS OFF)
+SELECT * FROM vegetables v,
+       (SELECT * FROM vegetables WHERE genus = 'daucus' OFFSET 0);
index 0c93f41ffff67d9a598e552682db1f4fa90827bf..2c9fb50b6107f9fb667ad7a3fdcf48a89ad441d5 100644 (file)
@@ -655,6 +655,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 16d200cfb4690dcbf32bb467d77f0d77bcc5bc59..a5b2314ef2a96999366b91eeb35282f1b10baf22 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 fb808823acf7f18392a1fd599a7b25dec9268165..c1e9397623b7195806de2ea6c0f34d65a05fc453 100644 (file)
@@ -208,6 +208,9 @@ typedef struct PlannerGlobal
        /* "flat" list of RTEPermissionInfos */
        List       *finalrteperminfos;
 
+       /* list of SubPlanRTInfo nodes */
+       List       *subrtinfos;
+
        /* "flat" list of PlanRowMarks */
        List       *finalrowmarks;
 
index 4bc6fb5670e4a14ac9f71a718d10d0c4803fe8d6..9ae72a607e22d22b80ca5750e7eab308fb5bc615 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;
+       char       *plan_name;
+       Index           rtoffset;
+       bool            dummy;
+} SubPlanRTInfo;
+
 #endif                                                 /* PLANNODES_H */
index df42b78bc9d38a57ec184267b6b41dcbf7c6cea9..e83ced4d746ab187ef511795c7e70c4bdfb58520 100644 (file)
@@ -2931,6 +2931,7 @@ SubLink
 SubLinkType
 SubOpts
 SubPlan
+SubPlanRTInfo
 SubPlanState
 SubRelInfo
 SubRemoveRels