fix various things so regression tests pass
authorRobert Haas <rhaas@postgresql.org>
Tue, 24 Jun 2025 14:39:28 +0000 (10:39 -0400)
committerRobert Haas <rhaas@postgresql.org>
Tue, 24 Jun 2025 14:39:28 +0000 (10:39 -0400)
contrib/pg_plan_advice/pgpa_join.c

index 2cea6d060daf2d26555298ec47ff6ef04e18f4c6..3a8546f8999747c40086f0d27f90aad75d073818 100644 (file)
@@ -34,6 +34,8 @@ static void pgpa_debug_out_join_member(StringInfo buf,
                                                                           pgpa_join_member *member);
 static char *pgpa_cstring_join_clump_strategy(pgpa_join_clump_strategy strategy);
 static char *pgpa_cstring_join_strategy(pgpa_join_strategy strategy);
+static bool is_result_node_with_child(Plan *plan);
+static bool is_sorting_plan(Plan *plan);
 
 /*
  * Should a given plan node be represented by a pgpa_unrolled_join, a
@@ -158,13 +160,31 @@ pgpa_unroll_join(PlannedStmt *pstmt, Plan *plan,
        /*
         * We need to pass the join_unroller object down through certain types of
         * plan nodes -- anything that's considered part of the join strategy,
-        * such as Hash Join's Hash node or a Materialize node inserted on the
-        * inner side of a nested loop as part of the join strategy -- and also
-        * Gather and Gather Merge nodes, which can occur in a join tree but are
-        * not themselves scans or joins.
+        * and any other nodes that can occur in a join tree despite not being
+        * scans or joins.
+        *
+        * This includes:
+        *
+        * (1) Materialize, Memoize, and Hash nodes, which are part of the join
+        * strategy,
+        *
+        * (2) Gather and Gather Merge nodes, which can occur at any point in
+        * the join tree where the planner decided to initiate parallelism,
+        *
+        * (3) Sort and IncrementalSort nodes, which can occur beneath MergeJoin
+        * or GatherMerge,
+        *
+        * (4) Agg and Unique nodes, which can occur when we decide to make
+        * the nullable side of a semijoin unique and then join the result, and
+        *
+        * (5) Result nodes with children, which can be added either to project
+        * to enforce a one-time filter (but Result nodes without children are
+        * degenerate scans or joins).
         */
-       if (IsA(plan, Material) || IsA(plan, Memoize) || IsA(plan, Hash) ||
-               IsA(plan, Sort) || IsA(plan, Gather) || IsA(plan, GatherMerge))
+       if (IsA(plan, Material) || IsA(plan, Memoize) || IsA(plan, Hash)
+               || IsA(plan, Gather) || IsA(plan, GatherMerge)
+               || is_sorting_plan(plan) || IsA(plan, Agg) || IsA(plan, Unique)
+               || is_result_node_with_child(plan))
        {
                *outer_join_unroller = join_unroller;
                return;
@@ -303,6 +323,32 @@ pgpa_destroy_join_unroller(pgpa_join_unroller *join_unroller)
        pfree(join_unroller);
 }
 
+static ElidedNode *
+pgpa_descend_node(PlannedStmt *pstmt, Plan **plan)
+{
+       *plan = (*plan)->lefttree;
+       return pgpa_last_elided_node(pstmt, *plan);
+}
+
+static ElidedNode *
+pgpa_descend_any_gather(PlannedStmt *pstmt, Plan **plan)
+{
+       if (IsA(*plan, Gather))
+               return pgpa_descend_node(pstmt, plan);
+
+       if (IsA(*plan, GatherMerge))
+       {
+               ElidedNode *elided = pgpa_descend_node(pstmt, plan);
+
+               if (elided == NULL && is_sorting_plan(*plan))
+                       elided = pgpa_descend_node(pstmt, plan);
+
+               return elided;
+       }
+
+       return NULL;
+}
+
 /*
  * Identify the join strategy used by a join and the "real" inner and outer
  * plans.
@@ -346,13 +392,13 @@ pgpa_decompose_join(PlannedStmt *pstmt, Plan *plan,
        switch (nodeTag(plan))
        {
                case T_MergeJoin:
-                       if (elidedouter == NULL && IsA(outerplan, Sort))
+                       if (elidedouter == NULL && is_sorting_plan(outerplan))
                        {
                                outerplan = outerplan->lefttree;
                                elidedouter = pgpa_last_elided_node(pstmt, outerplan);
                        }
 
-                       if (elidedouter == NULL && IsA(outerplan, Result))
+                       if (elidedouter == NULL && is_result_node_with_child(outerplan))
                        {
                                outerplan = outerplan->lefttree;
                                elidedouter = pgpa_last_elided_node(pstmt, outerplan);
@@ -367,13 +413,13 @@ pgpa_decompose_join(PlannedStmt *pstmt, Plan *plan,
                        else
                                strategy = JSTRAT_MERGE_JOIN_PLAIN;
 
-                       if (elidedinner == NULL && IsA(innerplan, Sort))
+                       if (elidedinner == NULL && is_sorting_plan(innerplan))
                        {
                                innerplan = innerplan->lefttree;
                                elidedinner = pgpa_last_elided_node(pstmt, innerplan);
                        }
 
-                       if (elidedinner == NULL && IsA(innerplan, Result))
+                       if (elidedinner == NULL && is_result_node_with_child(innerplan))
                        {
                                innerplan = innerplan->lefttree;
                                elidedinner = pgpa_last_elided_node(pstmt, innerplan);
@@ -410,14 +456,56 @@ pgpa_decompose_join(PlannedStmt *pstmt, Plan *plan,
        }
 
        if (elidedouter == NULL &&
-               (IsA(outerplan, Gather) || IsA(outerplan, GatherMerge)))
+               (IsA(outerplan, Agg) || IsA(outerplan, Unique)))
        {
                outerplan = outerplan->lefttree;
                elidedouter = pgpa_last_elided_node(pstmt, outerplan);
+
+               if (elidedouter == NULL && is_sorting_plan(outerplan))
+               {
+                       outerplan = outerplan->lefttree;
+                       elidedouter = pgpa_last_elided_node(pstmt, outerplan);
+               }
+
+               if (elidedouter == NULL && is_result_node_with_child(outerplan))
+               {
+                       outerplan = outerplan->lefttree;
+                       elidedouter = pgpa_last_elided_node(pstmt, outerplan);
+               }
        }
 
        if (elidedinner == NULL &&
-               (IsA(innerplan, Gather) || IsA(innerplan, GatherMerge)))
+               (IsA(innerplan, Agg) || IsA(innerplan, Unique)))
+       {
+               innerplan = innerplan->lefttree;
+               elidedinner = pgpa_last_elided_node(pstmt, innerplan);
+
+               if (elidedinner == NULL && is_sorting_plan(innerplan))
+               {
+                       outerplan = outerplan->lefttree;
+                       elidedouter = pgpa_last_elided_node(pstmt, outerplan);
+               }
+
+               if (elidedinner == NULL && is_result_node_with_child(innerplan))
+               {
+                       innerplan = innerplan->lefttree;
+                       elidedinner = pgpa_last_elided_node(pstmt, innerplan);
+               }
+       }
+
+       if (elidedouter == NULL)
+               elidedouter = pgpa_descend_any_gather(pstmt, &outerplan);
+
+       if (elidedinner == NULL)
+               elidedinner = pgpa_descend_any_gather(pstmt, &innerplan);
+
+       if (elidedouter == NULL && is_result_node_with_child(outerplan))
+       {
+               outerplan = outerplan->lefttree;
+               elidedouter = pgpa_last_elided_node(pstmt, outerplan);
+       }
+
+       if (elidedinner == NULL && is_result_node_with_child(innerplan))
        {
                innerplan = innerplan->lefttree;
                elidedinner = pgpa_last_elided_node(pstmt, innerplan);
@@ -680,3 +768,15 @@ pgpa_cstring_join_strategy(pgpa_join_strategy strategy)
 
        Assert(false);
 }
+
+static bool
+is_result_node_with_child(Plan *plan)
+{
+       return IsA(plan, Result) && plan->lefttree != NULL;
+}
+
+static bool
+is_sorting_plan(Plan *plan)
+{
+       return IsA(plan, Sort) || IsA(plan, IncrementalSort);
+}