Store information about elided nodes in the final plan.
authorRobert Haas <rhaas@postgresql.org>
Tue, 22 Apr 2025 18:10:19 +0000 (14:10 -0400)
committerRobert Haas <rhaas@postgresql.org>
Mon, 19 May 2025 15:02:50 +0000 (11:02 -0400)
When setrefs.c removes a SubqueryScan, single-child Append, or
single-child MergeAppend from the final Plan tree, the RTI which
would have been scanned by the removed node no longer appears in
the final plan (the actual range table entry is still present,
but it's no longer referenced).

That's fine for the executor, but it can create difficulties for
code that wants to deduce from the final plan what choices were
made during the planing process. For example, a traversal of a
join tree in the final plan might never encounter the RTI of one
of the relationss in the join problem, and might instead encounter
a scan of a child RTI or even one from a different subquery level.

This patch adjusts things so that each time we elide a node during
setrefs processing, we record the plan_node_id of its single surviving
child, the type of the removed node, and the RTIs that the removed
node would have scanned. This information is recorded in a separate
list that can be ignored by the executor and examined only by code
that cares about these details.

This commit also updates pg_overexplain to display these details.

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 dd8adddb4a31175d719e4839866b7edf190b9fc2..0674641bec1ca885d584d20d7875a4c34dce4031 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)
index 5dc707d69e32ca4e7343f3c9f543c691f5a6c8e0..fa907fa472e0f7a2ab3f22c88d3cdd880f18af59 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 5dcd09e712a756184169b2eeb27a575366429a1b..9ee99fdb6bd7baed42e0ce737bbc46269ccd3f7d 100644 (file)
@@ -583,6 +583,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 6f0d97f3936a28d911a67ce68031d17cadb905d4..5ef353c1563c4b59bee8df776b906ae6dfc290d6 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);
+
 
 /*****************************************************************************
  *
@@ -1439,10 +1442,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
        {
@@ -1870,7 +1880,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;
+               }
        }
 
        /*
@@ -1938,7 +1958,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;
+               }
        }
 
        /*
@@ -3753,3 +3783,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
+ * procesing, 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 61ba04d014f9d92aa971d92672597de543aaecde..957ee4208a9755019f09641fb21c2b5e35859785 100644 (file)
@@ -162,6 +162,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 9df11cd394a0fa74ef8d55f47a316cb6e9c43008..83fb4bd8707e249099657863745e49a947702b1a 100644 (file)
@@ -141,6 +141,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;
+
        /* statement location in source string (copied from Query) */
        /* start location, or -1 if unknown */
        ParseLoc        stmt_location;
@@ -1797,4 +1800,18 @@ 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.
+ */
+typedef struct ElidedNode
+{
+       NodeTag         type;
+       int                     plan_node_id;
+       NodeTag         elided_type;
+       Bitmapset  *relids;
+} ElidedNode;
+
 #endif                                                 /* PLANNODES_H */
index 1c6a7252ee49527ff2b4559a17e343f8ce6b2d4c..f4ae78224ab8ecd77dcf5802aee2ded87c1233d0 100644 (file)
@@ -4310,3 +4310,4 @@ ExplainExtensionOption
 ExplainOptionHandler
 overexplain_options
 SubPlanRTInfo
+ElidedNode