pgpa_identifier.o \
pgpa_join.o \
pgpa_output.o \
+ pgpa_scan.o \
pgpa_walker.o
EXTENSION = pg_plan_advice
'pgpa_identifier.c',
'pgpa_join.c',
'pgpa_output.c',
+ 'pgpa_scan.c',
'pgpa_walker.c',
)
/*-------------------------------------------------------------------------
*
- * pgpa_join.h
+ * pgpa_join.c
* analysis of joins in Plan trees
*
* Copyright (c) 2016-2025, PostgreSQL Global Development Group
#include "postgres.h"
#include "pgpa_join.h"
+#include "pgpa_scan.h"
#include "pgpa_walker.h"
#include "nodes/pathnodes.h"
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.
*
#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
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;
} 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,
#include "postgres.h"
#include "pgpa_output.h"
+#include "pgpa_scan.h"
typedef struct pgpa_output_context
{
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+}
#include "postgres.h"
#include "pgpa_join.h"
+#include "pgpa_scan.h"
#include "pgpa_walker.h"
#include "nodes/plannodes.h"