]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Store information about elided nodes in the final plan.
authorRobert Haas <rhaas@postgresql.org>
Tue, 10 Feb 2026 21:46:05 +0000 (16:46 -0500)
committerRobert Haas <rhaas@postgresql.org>
Tue, 10 Feb 2026 21:46:05 +0000 (16:46 -0500)
An extension (or core code) might want to reconstruct the planner's
choice of join order from the final plan. To do so, it must be possible
to find all of the RTIs that were part of the join problem in that plan.
Commit adbad833f3d9e9176e8d7005f15ea6056900227d, together with the
earlier work in 8c49a484e8ebb0199fba4bd68eaaedaf49b48ed0, is enough to
let us match up RTIs we see in the final plan with RTIs that we see
during the planning cycle, but we still have a problem if the planner
decides to drop some RTIs out of the final plan altogether.

To fix that, when setrefs.c removes a SubqueryScan, single-child Append,
or single-child MergeAppend from the final Plan tree, record the type of
the removed node and the RTIs that the removed node would have scanned
in the final plan tree. It would be natural to record this information
on the child of the removed plan node, but that would require adding an
additional pointer field to type Plan, which seems undesirable.  So,
instead, store the information in a separate list that the executor need
never consult, and use the plan_node_id to identify the plan node with
which the removed node is logically associated.

Also, update pg_overexplain to display these details.

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
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 f4ce828bc62a40a8abb43b1af85fae7f9c615b26..198bbe10d7322c11be6bdbb80da9dff63cd98616 100644 (file)
@@ -452,6 +452,8 @@ SELECT * FROM vegetables WHERE genus = 'daucus';
  Seq Scan on daucus vegetables
    Filter: (genus = 'daucus'::text)
    Scan RTI: 2
+   Elided Node Type: Append
+   Elided Node RTIs: 1
  RTI 1 (relation, inherited, in-from-clause):
    Eref: vegetables (id, name, genus)
    Relation: vegetables
@@ -465,7 +467,7 @@ SELECT * FROM vegetables WHERE genus = 'daucus';
    Relation Kind: relation
    Relation Lock Mode: AccessShareLock
  Unprunable RTIs: 1 2
-(16 rows)
+(18 rows)
 
 -- Also test a case that involves a write.
 EXPLAIN (RANGE_TABLE, COSTS OFF)
@@ -499,6 +501,10 @@ SELECT * FROM vegetables v,
    ->  Seq Scan on daucus vegetables
          Filter: (genus = 'daucus'::text)
          Scan RTI: 6
+         Elided Node Type: Append
+         Elided Node RTIs: 5
+         Elided Node Type: SubqueryScan
+         Elided Node RTIs: 2
    ->  Append
          Append RTIs: 1
          ->  Seq Scan on brassica v_1
@@ -542,7 +548,7 @@ SELECT * FROM vegetables v,
    Relation Kind: relation
    Relation Lock Mode: AccessShareLock
  Unprunable RTIs: 1 3 4 5 6
-(47 rows)
+(51 rows)
 
 -- should show "Subplan: unnamed_subquery"
 EXPLAIN (RANGE_TABLE, COSTS OFF)
@@ -554,6 +560,10 @@ SELECT * FROM vegetables v,
    ->  Seq Scan on daucus vegetables
          Filter: (genus = 'daucus'::text)
          Scan RTI: 6
+         Elided Node Type: Append
+         Elided Node RTIs: 5
+         Elided Node Type: SubqueryScan
+         Elided Node RTIs: 2
    ->  Append
          Append RTIs: 1
          ->  Seq Scan on brassica v_1
@@ -596,5 +606,5 @@ SELECT * FROM vegetables v,
    Relation Kind: relation
    Relation Lock Mode: AccessShareLock
  Unprunable RTIs: 1 3 4 5 6
-(46 rows)
+(50 rows)
 
index bf8c768ed47aad6ca3fd86abbbb546d5e8c93860..e0184ba314ac5b485d413d5d468b8874302241bb 100644 (file)
@@ -191,6 +191,8 @@ overexplain_per_node_hook(PlanState *planstate, List *ancestors,
         */
        if (options->range_table)
        {
+               bool            opened_elided_nodes = false;
+
                switch (nodeTag(plan))
                {
                        case T_SeqScan:
@@ -251,6 +253,43 @@ overexplain_per_node_hook(PlanState *planstate, List *ancestors,
                        default:
                                break;
                }
+
+               foreach_node(ElidedNode, n, es->pstmt->elidedNodes)
+               {
+                       char       *elidednodetag;
+
+                       if (n->plan_node_id != plan->plan_node_id)
+                               continue;
+
+                       if (!opened_elided_nodes)
+                       {
+                               ExplainOpenGroup("Elided Nodes", "Elided Nodes", false, es);
+                               opened_elided_nodes = true;
+                       }
+
+                       switch (n->elided_type)
+                       {
+                               case T_Append:
+                                       elidednodetag = "Append";
+                                       break;
+                               case T_MergeAppend:
+                                       elidednodetag = "MergeAppend";
+                                       break;
+                               case T_SubqueryScan:
+                                       elidednodetag = "SubqueryScan";
+                                       break;
+                               default:
+                                       elidednodetag = psprintf("%d", n->elided_type);
+                                       break;
+                       }
+
+                       ExplainOpenGroup("Elided Node", NULL, true, es);
+                       ExplainPropertyText("Elided Node Type", elidednodetag, es);
+                       overexplain_bitmapset("Elided Node RTIs", n->relids, es);
+                       ExplainCloseGroup("Elided Node", NULL, true, es);
+               }
+               if (opened_elided_nodes)
+                       ExplainCloseGroup("Elided Nodes", "Elided Nodes", false, es);
        }
 }
 
index 2c9fb50b6107f9fb667ad7a3fdcf48a89ad441d5..f68142cfcb8d2405fbd2ac625d654e102c0b7bc8 100644 (file)
@@ -666,6 +666,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
        result->paramExecTypes = glob->paramExecTypes;
        /* utilityStmt should be null, but we might as well copy it */
        result->utilityStmt = parse->utilityStmt;
+       result->elidedNodes = glob->elidedNodes;
        result->stmt_location = parse->stmt_location;
        result->stmt_len = parse->stmt_len;
 
index a5b2314ef2a96999366b91eeb35282f1b10baf22..5ad6c13830bbe0c62094bad094d0ca8b78f02e0b 100644 (file)
@@ -211,6 +211,9 @@ static List *set_windowagg_runcondition_references(PlannerInfo *root,
                                                                                                   List *runcondition,
                                                                                                   Plan *plan);
 
+static void record_elided_node(PlannerGlobal *glob, int plan_node_id,
+                                                          NodeTag elided_type, Bitmapset *relids);
+
 
 /*****************************************************************************
  *
@@ -1460,10 +1463,17 @@ set_subqueryscan_references(PlannerInfo *root,
 
        if (trivial_subqueryscan(plan))
        {
+               Index           scanrelid;
+
                /*
                 * We can omit the SubqueryScan node and just pull up the subplan.
                 */
                result = clean_up_removed_plan_level((Plan *) plan, plan->subplan);
+
+               /* Remember that we removed a SubqueryScan */
+               scanrelid = plan->scan.scanrelid + rtoffset;
+               record_elided_node(root->glob, plan->subplan->plan_node_id,
+                                                  T_SubqueryScan, bms_make_singleton(scanrelid));
        }
        else
        {
@@ -1891,7 +1901,17 @@ set_append_references(PlannerInfo *root,
                Plan       *p = (Plan *) linitial(aplan->appendplans);
 
                if (p->parallel_aware == aplan->plan.parallel_aware)
-                       return clean_up_removed_plan_level((Plan *) aplan, p);
+               {
+                       Plan       *result;
+
+                       result = clean_up_removed_plan_level((Plan *) aplan, p);
+
+                       /* Remember that we removed an Append */
+                       record_elided_node(root->glob, p->plan_node_id, T_Append,
+                                                          offset_relid_set(aplan->apprelids, rtoffset));
+
+                       return result;
+               }
        }
 
        /*
@@ -1959,7 +1979,17 @@ set_mergeappend_references(PlannerInfo *root,
                Plan       *p = (Plan *) linitial(mplan->mergeplans);
 
                if (p->parallel_aware == mplan->plan.parallel_aware)
-                       return clean_up_removed_plan_level((Plan *) mplan, p);
+               {
+                       Plan       *result;
+
+                       result = clean_up_removed_plan_level((Plan *) mplan, p);
+
+                       /* Remember that we removed a MergeAppend */
+                       record_elided_node(root->glob, p->plan_node_id, T_MergeAppend,
+                                                          offset_relid_set(mplan->apprelids, rtoffset));
+
+                       return result;
+               }
        }
 
        /*
@@ -3774,3 +3804,21 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
        return expression_tree_walker(node, extract_query_dependencies_walker,
                                                                  context);
 }
+
+/*
+ * Record some details about a node removed from the plan during setrefs
+ * processing, for the benefit of code trying to reconstruct planner decisions
+ * from examination of the final plan tree.
+ */
+static void
+record_elided_node(PlannerGlobal *glob, int plan_node_id,
+                                  NodeTag elided_type, Bitmapset *relids)
+{
+       ElidedNode *n = makeNode(ElidedNode);
+
+       n->plan_node_id = plan_node_id;
+       n->elided_type = elided_type;
+       n->relids = relids;
+
+       glob->elidedNodes = lappend(glob->elidedNodes, n);
+}
index c1e9397623b7195806de2ea6c0f34d65a05fc453..9cc5d2e7411c61e1fcc112bb7024d88322aec689 100644 (file)
@@ -232,6 +232,9 @@ typedef struct PlannerGlobal
        /* type OIDs for PARAM_EXEC Params */
        List       *paramExecTypes;
 
+       /* info about nodes elided from the plan during setrefs processing */
+       List       *elidedNodes;
+
        /* highest PlaceHolderVar ID assigned */
        Index           lastPHId;
 
index 9ae72a607e22d22b80ca5750e7eab308fb5bc615..0ad0ff404c94241d53c6e0fe5b363f570807b6df 100644 (file)
@@ -152,6 +152,9 @@ typedef struct PlannedStmt
        /* non-null if this is utility stmt */
        Node       *utilityStmt;
 
+       /* info about nodes elided from the plan during setrefs processing */
+       List       *elidedNodes;
+
        /*
         * DefElem objects added by extensions, e.g. using planner_shutdown_hook
         *
@@ -1838,4 +1841,21 @@ typedef struct SubPlanRTInfo
        bool            dummy;
 } SubPlanRTInfo;
 
+/*
+ * ElidedNode
+ *
+ * Information about nodes elided from the final plan tree: trivial subquery
+ * scans, and single-child Append and MergeAppend nodes.
+ *
+ * plan_node_id is that of the surviving plan node, the sole child of the
+ * one which was elided.
+ */
+typedef struct ElidedNode
+{
+       NodeTag         type;
+       int                     plan_node_id;
+       NodeTag         elided_type;
+       Bitmapset  *relids;
+} ElidedNode;
+
 #endif                                                 /* PLANNODES_H */
index e83ced4d746ab187ef511795c7e70c4bdfb58520..523977721ea558a7ffa70b75e6f3de98f475ca56 100644 (file)
@@ -710,6 +710,7 @@ EachState
 Edge
 EditableObjectType
 ElementsState
+ElidedNode
 EnableTimeoutParams
 EndDataPtrType
 EndDirectModify_function