reclassify clumped joins as scans
authorRobert Haas <rhaas@postgresql.org>
Wed, 16 Jul 2025 13:24:46 +0000 (09:24 -0400)
committerRobert Haas <rhaas@postgresql.org>
Wed, 16 Jul 2025 13:25:56 +0000 (09:25 -0400)
contrib/pg_plan_advice/Makefile
contrib/pg_plan_advice/meson.build
contrib/pg_plan_advice/pgpa_join.c
contrib/pg_plan_advice/pgpa_join.h
contrib/pg_plan_advice/pgpa_output.c
contrib/pg_plan_advice/pgpa_scan.c [new file with mode: 0644]
contrib/pg_plan_advice/pgpa_walker.c

index afeea2e5dd78a2086f396ae1ac759bf7a4c17630..6f8127e5516704eefd528de2accc64cdc2f397c7 100644 (file)
@@ -8,6 +8,7 @@ OBJS = \
        pgpa_identifier.o \
        pgpa_join.o \
        pgpa_output.o \
+       pgpa_scan.o \
        pgpa_walker.o
 
 EXTENSION = pg_plan_advice
index db510082d6513611cc132e5626254d71f3d4a56b..d9e3ec148c7b3b09ffd6f1ebc818d85248f8ab60 100644 (file)
@@ -6,6 +6,7 @@ pg_plan_advice_sources = files(
   'pgpa_identifier.c',
   'pgpa_join.c',
   'pgpa_output.c',
+  'pgpa_scan.c',
   'pgpa_walker.c',
 )
 
index 64f8dfdb8accf4522d3ee70db3023a5069a6d970..392eea4dd2b12fbce95830ff773647044db4625a 100644 (file)
@@ -1,6 +1,6 @@
 /*-------------------------------------------------------------------------
  *
- * pgpa_join.h
+ * pgpa_join.c
  *       analysis of joins in Plan trees
  *
  * Copyright (c) 2016-2025, PostgreSQL Global Development Group
@@ -13,6 +13,7 @@
 #include "postgres.h"
 
 #include "pgpa_join.h"
+#include "pgpa_scan.h"
 #include "pgpa_walker.h"
 
 #include "nodes/pathnodes.h"
@@ -76,82 +77,6 @@ pgpa_get_join_class(Plan *plan)
        return PGPA_NON_JOIN;
 }
 
-/*
- * Build a pgpa_clumped_join object for a Plan node.
- *
- * If there is at least one ElidedNode for this plan node, pass the uppermost
- * one as elided_node, else pass NULL.
- */
-pgpa_clumped_join *
-pgpa_build_clumped_join(PlannedStmt *pstmt, Plan *plan,
-                                               ElidedNode *elided_node)
-{
-       pgpa_clumped_join *clump = palloc(sizeof(pgpa_clumped_join));
-       Bitmapset *relids = NULL;
-       int rti = -1;
-
-       clump->plan = plan;
-
-       if (elided_node != NULL)
-       {
-               NodeTag         elided_type = elided_node->elided_type;
-
-               /*
-                * The only case we expect to encounter here is a partitionwise join
-                * whose Append or MergeAppend node was elided due to having only one
-                * surviving child. It's also possiblee for a trivial SubqueryScan to
-                * be elided, but in that case we expected only a single RTI, in which
-                * case it's not a join.
-                */
-               Assert(bms_membership(elided_node->relids) == BMS_MULTIPLE);
-               relids = elided_node->relids;
-               if (elided_type == T_Append || elided_type == T_MergeAppend)
-                       clump->strategy = JSTRAT_CLUMP_PARTITIONWISE;
-               else
-                       elog(ERROR, "unexpected elided node type");
-       }
-       else
-       {
-               Assert(pgpa_get_join_class(plan) == PGPA_CLUMPED_JOIN);
-
-               relids = pgpa_relids(plan);
-
-               if (IsA(plan, Result))
-                       clump->strategy = JSTRAT_CLUMP_DEGENERATE;
-               else if (IsA(plan, ForeignScan))
-                       clump->strategy = JSTRAT_CLUMP_FOREIGN;
-               else if (IsA(plan, Append))
-                       clump->strategy = JSTRAT_CLUMP_PARTITIONWISE;
-               else if (IsA(plan, MergeAppend))
-                       clump->strategy = JSTRAT_CLUMP_PARTITIONWISE;
-               else
-                       elog(ERROR, "unexpected plan type");
-       }
-
-       /*
-        * Filter out any RTIs of type RTE_JOIN, since we have no use for them,
-        * and don't want them creating confusion later. (We always refer to
-        * groups of relations in terms of the relation RTIs, not the join RTIs.)
-        */
-       clump->relids = NULL;
-       while ((rti = bms_next_member(relids, rti)) >= 0)
-       {
-               RangeTblEntry *rte = rt_fetch(rti, pstmt->rtable);
-
-               if (rte->rtekind != RTE_JOIN)
-                       clump->relids = bms_add_member(clump->relids, rti);
-       }
-
-       /*
-        * We concluded that this was a join based on the fact that there were
-        * multiple RTIs -- and even after removing the join RTIs, that should
-        * still be the case.
-        */
-       Assert(bms_membership(clump->relids) == BMS_MULTIPLE);
-
-       return clump;
-}
-
 /*
  * Create an initially-empty object for unrolling joins.
  *
index 3a8585c6164c27828ce9065f471c09e452dec02f..e7dabbae2f114a0866d03de8c06793806cac3827 100644 (file)
 #include "nodes/plannodes.h"
 
 struct pgpa_plan_walker_context;
+struct pgpa_clumped_join;
 typedef struct pgpa_join_unroller pgpa_join_unroller;
 typedef struct pgpa_unrolled_join pgpa_unrolled_join;
 
-/*
- * Certain types of plan nodes can join any number of input relations in
- * a single step; we call these "clumped joins".
- *
- * For our purposes, the important thing about a clumped join is that we
- * can't meaningfully speak about the order in which tables are joined
- * within a single clump. For example, if the optimizer chooses a
- * partitionwise join on tables A and B, we can't say whether A was joined
- * to B or whether B was joined to A; instead, each pair of child tables
- * has its own join order. Likewise, if a foreign data wrapper pushes a
- * join to the remote side, we don't know the join order.
- *
- * JSTRAT_CLUMP_DEGENERATE refers to the case where several relations are
- * all proven empty and replaced with a single Result node. Here again, while
- * the Result node may be joined to other things and we can speak about its
- * place within the larger join order, we can't speak about a join ordering
- * within the Result node itself.
- */
-typedef enum
-{
-       JSTRAT_CLUMP_DEGENERATE = 0,
-       JSTRAT_CLUMP_FOREIGN,
-       JSTRAT_CLUMP_PARTITIONWISE
-       /* update NUM_PGPA_CLUMP_JOIN_STRATEGY if you add anything here */
-} pgpa_join_clump_strategy;
-
-#define NUM_PGPA_CLUMP_JOIN_STRATEGY   ((int) JSTRAT_CLUMP_PARTITIONWISE + 1)
-
-/*
- * All of the details we need regarding a clumped join.
- */
-typedef struct
-{
-       Plan       *plan;
-       pgpa_join_clump_strategy strategy;
-       Bitmapset  *relids;
-} pgpa_clumped_join;
-
 /*
  * Although there are three main join strategies, we try to classify things
  * more precisely here: merge joins have the option of using materialization
@@ -97,7 +60,7 @@ typedef struct
        Plan       *plan;
        ElidedNode *elided_node;
        Index           rti;
-       pgpa_clumped_join *clump_join;
+       struct pgpa_clumped_join *clump_join;
        pgpa_unrolled_join *unrolled_join;
 } pgpa_join_member;
 
@@ -135,9 +98,6 @@ typedef enum
 } pgpa_join_class;
 
 extern pgpa_join_class pgpa_get_join_class(Plan *plan);
-extern pgpa_clumped_join *pgpa_build_clumped_join(PlannedStmt *pstmt,
-                                                                                                 Plan *plan,
-                                                                                                 ElidedNode *elided_node);
 
 extern pgpa_join_unroller *pgpa_create_join_unroller(void);
 extern void pgpa_unroll_join(struct pgpa_plan_walker_context *walker,
index 4fe389038163d0b99f873de2ef594ae0b60c7b07..634cd75af52798d9a70a7506162e150739e5ba9e 100644 (file)
@@ -13,6 +13,7 @@
 #include "postgres.h"
 
 #include "pgpa_output.h"
+#include "pgpa_scan.h"
 
 typedef struct pgpa_output_context
 {
diff --git a/contrib/pg_plan_advice/pgpa_scan.c b/contrib/pg_plan_advice/pgpa_scan.c
new file mode 100644 (file)
index 0000000..41686dd
--- /dev/null
@@ -0,0 +1,94 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgpa_scan.c
+ *       analysis of scans in Plan trees
+ *
+ * Copyright (c) 2016-2025, PostgreSQL Global Development Group
+ *
+ *       contrib/pg_plan_advice/pgpa_scan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "pgpa_scan.h"
+#include "pgpa_walker.h"
+
+#include "nodes/parsenodes.h"
+#include "parser/parsetree.h"
+
+/*
+ * Build a pgpa_clumped_join object for a Plan node.
+ *
+ * If there is at least one ElidedNode for this plan node, pass the uppermost
+ * one as elided_node, else pass NULL.
+ */
+pgpa_clumped_join *
+pgpa_build_clumped_join(PlannedStmt *pstmt, Plan *plan,
+                                               ElidedNode *elided_node)
+{
+       pgpa_clumped_join *clump = palloc(sizeof(pgpa_clumped_join));
+       Bitmapset *relids = NULL;
+       int rti = -1;
+
+       clump->plan = plan;
+
+       if (elided_node != NULL)
+       {
+               NodeTag         elided_type = elided_node->elided_type;
+
+               /*
+                * The only case we expect to encounter here is a partitionwise join
+                * whose Append or MergeAppend node was elided due to having only one
+                * surviving child. It's also possiblee for a trivial SubqueryScan to
+                * be elided, but in that case we expected only a single RTI, in which
+                * case it's not a join.
+                */
+               Assert(bms_membership(elided_node->relids) == BMS_MULTIPLE);
+               relids = elided_node->relids;
+               if (elided_type == T_Append || elided_type == T_MergeAppend)
+                       clump->strategy = JSTRAT_CLUMP_PARTITIONWISE;
+               else
+                       elog(ERROR, "unexpected elided node type");
+       }
+       else
+       {
+               Assert(pgpa_get_join_class(plan) == PGPA_CLUMPED_JOIN);
+
+               relids = pgpa_relids(plan);
+
+               if (IsA(plan, Result))
+                       clump->strategy = JSTRAT_CLUMP_DEGENERATE;
+               else if (IsA(plan, ForeignScan))
+                       clump->strategy = JSTRAT_CLUMP_FOREIGN;
+               else if (IsA(plan, Append))
+                       clump->strategy = JSTRAT_CLUMP_PARTITIONWISE;
+               else if (IsA(plan, MergeAppend))
+                       clump->strategy = JSTRAT_CLUMP_PARTITIONWISE;
+               else
+                       elog(ERROR, "unexpected plan type");
+       }
+
+       /*
+        * Filter out any RTIs of type RTE_JOIN, since we have no use for them,
+        * and don't want them creating confusion later. (We always refer to
+        * groups of relations in terms of the relation RTIs, not the join RTIs.)
+        */
+       clump->relids = NULL;
+       while ((rti = bms_next_member(relids, rti)) >= 0)
+       {
+               RangeTblEntry *rte = rt_fetch(rti, pstmt->rtable);
+
+               if (rte->rtekind != RTE_JOIN)
+                       clump->relids = bms_add_member(clump->relids, rti);
+       }
+
+       /*
+        * We concluded that this was a join based on the fact that there were
+        * multiple RTIs -- and even after removing the join RTIs, that should
+        * still be the case.
+        */
+       Assert(bms_membership(clump->relids) == BMS_MULTIPLE);
+
+       return clump;
+}
index 8973180379e84bcfc8b38b93b26ef2b57715c132..3d20d6cc053bde02476bc0b7128cefebdffc1341 100644 (file)
@@ -1,6 +1,7 @@
 #include "postgres.h"
 
 #include "pgpa_join.h"
+#include "pgpa_scan.h"
 #include "pgpa_walker.h"
 
 #include "nodes/plannodes.h"