PostgreSQL Source Code git master
Loading...
Searching...
No Matches
pgpa_scan.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * pgpa_scan.c
4 * analysis of scans in Plan trees
5 *
6 * Copyright (c) 2016-2026, PostgreSQL Global Development Group
7 *
8 * contrib/pg_plan_advice/pgpa_scan.c
9 *
10 *-------------------------------------------------------------------------
11 */
12#include "postgres.h"
13
14#include "pgpa_scan.h"
15#include "pgpa_walker.h"
16
17#include "nodes/parsenodes.h"
18#include "parser/parsetree.h"
19
21 pgpa_scan_strategy strategy,
22 Bitmapset *relids);
23
24
25static RTEKind unique_nonjoin_rtekind(Bitmapset *relids, List *rtable);
26
27/*
28 * Build a pgpa_scan object for a Plan node and update the plan walker
29 * context as appropriate. If this is an Append or MergeAppend scan, also
30 * build pgpa_scan for any scans that were consolidated into this one by
31 * Append/MergeAppend pull-up.
32 *
33 * If there is at least one ElidedNode for this plan node, pass the uppermost
34 * one as elided_node, else pass NULL.
35 *
36 * Set the 'beneath_any_gather' node if we are underneath a Gather or
37 * Gather Merge node (except for a single-copy Gather node, for which
38 * GATHER or GATHER_MERGE advice should not be emitted).
39 *
40 * Set the 'within_join_problem' flag if we're inside of a join problem and
41 * not otherwise.
42 */
45 ElidedNode *elided_node,
47{
49 Bitmapset *relids = NULL;
50 int rti = -1;
51 List *child_append_relid_sets = NIL;
53
54 if (elided_node != NULL)
55 {
56 nodetype = elided_node->elided_type;
57 relids = elided_node->relids;
58
59 /*
60 * If setrefs processing elided an Append or MergeAppend node that had
61 * only one surviving child, it could be either a partitionwise
62 * operation or a setop over subqueries, depending on the rtekind.
63 *
64 * A setop over subqueries, or a trivial SubqueryScan that was elided,
65 * is an "ordinary" scan i.e. one for which we do not need to generate
66 * advice because the planner has not made any meaningful choice.
67 *
68 * Note that the PGPA_SCAN_PARTITIONWISE case also includes
69 * partitionwise joins; this module considers those to be a form of
70 * scan, since they lack internal structure that we can decompose.
71 *
72 * Note also that it's possible for relids to be NULL here, if the
73 * elided Append node is part of a partitionwise aggregate. In that
74 * case, it doesn't matter what strategy we choose, but we do need to
75 * avoid calling unique_nonjoin_rtekind(), which would fail an
76 * assertion.
77 */
78 if ((nodetype == T_Append || nodetype == T_MergeAppend) &&
79 relids != NULL &&
81 walker->pstmt->rtable) == RTE_RELATION)
82 strategy = PGPA_SCAN_PARTITIONWISE;
83 else
84 strategy = PGPA_SCAN_ORDINARY;
85
86 /* Join RTIs can be present, but advice never refers to them. */
87 relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
88 }
89 else if ((rti = pgpa_scanrelid(plan)) != 0)
90 {
91 relids = bms_make_singleton(rti);
92
93 switch (nodeTag(plan))
94 {
95 case T_SeqScan:
96 strategy = PGPA_SCAN_SEQ;
97 break;
99 strategy = PGPA_SCAN_BITMAP_HEAP;
100 break;
101 case T_IndexScan:
102 strategy = PGPA_SCAN_INDEX;
103 break;
104 case T_IndexOnlyScan:
105 strategy = PGPA_SCAN_INDEX_ONLY;
106 break;
107 case T_TidScan:
108 case T_TidRangeScan:
109 strategy = PGPA_SCAN_TID;
110 break;
111 default:
112
113 /*
114 * This case includes a ForeignScan targeting a single
115 * relation; no other strategy is possible in that case, but
116 * see below, where things are different in multi-relation
117 * cases.
118 */
119 strategy = PGPA_SCAN_ORDINARY;
120 break;
121 }
122 }
123 else if ((relids = pgpa_relids(plan)) != NULL)
124 {
125 switch (nodeTag(plan))
126 {
127 case T_ForeignScan:
128
129 /*
130 * If multiple relations are being targeted by a single
131 * foreign scan, then the foreign join has been pushed to the
132 * remote side, and we want that to be reflected in the
133 * generated advice.
134 */
135 strategy = PGPA_SCAN_FOREIGN;
136 break;
137 case T_Append:
138
139 /*
140 * Append nodes can represent partitionwise scans of a
141 * relation, but when they implement a set operation, they are
142 * just ordinary scans.
143 */
144 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
145 == RTE_RELATION)
146 strategy = PGPA_SCAN_PARTITIONWISE;
147 else
148 strategy = PGPA_SCAN_ORDINARY;
149
150 /* Be sure to account for pulled-up scans. */
151 child_append_relid_sets =
152 ((Append *) plan)->child_append_relid_sets;
153 break;
154 case T_MergeAppend:
155 /* Same logic here as for Append, above. */
156 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
157 == RTE_RELATION)
158 strategy = PGPA_SCAN_PARTITIONWISE;
159 else
160 strategy = PGPA_SCAN_ORDINARY;
161
162 /* Be sure to account for pulled-up scans. */
163 child_append_relid_sets =
164 ((MergeAppend *) plan)->child_append_relid_sets;
165 break;
166 default:
167 strategy = PGPA_SCAN_ORDINARY;
168 break;
169 }
170
171
172 /* Join RTIs can be present, but advice never refers to them. */
173 relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
174 }
175
176 /*
177 * If this is an Append or MergeAppend node into which subordinate Append
178 * or MergeAppend paths were merged, each of those merged paths is
179 * effectively another scan for which we need to account.
180 */
181 foreach_node(Bitmapset, child_relids, child_append_relid_sets)
182 {
184
186 pgpa_filter_out_join_relids(child_relids,
187 walker->pstmt->rtable);
188 (void) pgpa_make_scan(walker, plan, strategy,
190 }
191
192 /*
193 * If this plan node has no associated RTIs, it's not a scan. When the
194 * 'within_join_problem' flag is set, that's unexpected, so throw an
195 * error, else return quietly.
196 */
197 if (relids == NULL)
198 {
200 elog(ERROR, "plan node has no RTIs: %d", (int) nodeTag(plan));
201 return NULL;
202 }
203
204 /*
205 * Add the appropriate set of RTIs to walker->no_gather_scans.
206 *
207 * Add nothing if we're beneath a Gather or Gather Merge node, since
208 * NO_GATHER advice is clearly inappropriate in that situation.
209 *
210 * Add nothing if this is an Append or MergeAppend node, whether or not
211 * elided. We'll emit NO_GATHER() for the underlying scan, which is good
212 * enough.
213 */
216 walker->no_gather_scans =
217 bms_add_members(walker->no_gather_scans, relids);
218
219 /* Caller tells us whether NO_GATHER() advice for this scan is needed. */
220 return pgpa_make_scan(walker, plan, strategy, relids);
221}
222
223/*
224 * Create a single pgpa_scan object and update the pgpa_plan_walker_context.
225 */
226static pgpa_scan *
228 pgpa_scan_strategy strategy, Bitmapset *relids)
229{
230 pgpa_scan *scan;
231
232 /* Create the scan object. */
233 scan = palloc(sizeof(pgpa_scan));
234 scan->plan = plan;
235 scan->strategy = strategy;
236 scan->relids = relids;
237
238 /* Add it to the appropriate list. */
239 walker->scans[scan->strategy] = lappend(walker->scans[scan->strategy],
240 scan);
241
242 return scan;
243}
244
245/*
246 * Determine the unique rtekind of a set of relids.
247 */
248static RTEKind
250{
251 int rti = -1;
252 bool first = true;
253 RTEKind rtekind = RTE_RELATION; /* silence compiler warning */
254
255 Assert(relids != NULL);
256
257 while ((rti = bms_next_member(relids, rti)) >= 0)
258 {
259 RangeTblEntry *rte = rt_fetch(rti, rtable);
260
261 if (rte->rtekind == RTE_JOIN)
262 continue;
263
264 if (first)
265 {
266 rtekind = rte->rtekind;
267 first = false;
268 }
269 else if (rtekind != rte->rtekind)
270 elog(ERROR, "rtekind mismatch: %d vs. %d",
271 rtekind, rte->rtekind);
272 }
273
274 if (first)
275 elog(ERROR, "no non-RTE_JOIN RTEs found");
276
277 return rtekind;
278}
Bitmapset * bms_make_singleton(int x)
Definition bitmapset.c:216
int bms_next_member(const Bitmapset *a, int prevbit)
Definition bitmapset.c:1290
Bitmapset * bms_add_members(Bitmapset *a, const Bitmapset *b)
Definition bitmapset.c:901
#define Assert(condition)
Definition c.h:943
#define ERROR
Definition elog.h:39
#define elog(elevel,...)
Definition elog.h:227
List * lappend(List *list, void *datum)
Definition list.c:339
void * palloc(Size size)
Definition mcxt.c:1387
#define nodeTag(nodeptr)
Definition nodes.h:139
NodeTag
Definition nodes.h:27
RTEKind
@ RTE_JOIN
@ RTE_RELATION
#define rt_fetch(rangetable_index, rangetable)
Definition parsetree.h:31
#define NIL
Definition pg_list.h:68
#define foreach_node(type, var, lst)
Definition pg_list.h:528
#define plan(x)
Definition pg_regress.c:164
static RTEKind unique_nonjoin_rtekind(Bitmapset *relids, List *rtable)
Definition pgpa_scan.c:249
pgpa_scan * pgpa_build_scan(pgpa_plan_walker_context *walker, Plan *plan, ElidedNode *elided_node, bool beneath_any_gather, bool within_join_problem)
Definition pgpa_scan.c:44
static pgpa_scan * pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan, pgpa_scan_strategy strategy, Bitmapset *relids)
Definition pgpa_scan.c:227
pgpa_scan_strategy
Definition pgpa_scan.h:56
@ PGPA_SCAN_SEQ
Definition pgpa_scan.h:58
@ PGPA_SCAN_INDEX
Definition pgpa_scan.h:61
@ PGPA_SCAN_INDEX_ONLY
Definition pgpa_scan.h:62
@ PGPA_SCAN_BITMAP_HEAP
Definition pgpa_scan.h:59
@ PGPA_SCAN_FOREIGN
Definition pgpa_scan.h:60
@ PGPA_SCAN_TID
Definition pgpa_scan.h:64
@ PGPA_SCAN_PARTITIONWISE
Definition pgpa_scan.h:63
@ PGPA_SCAN_ORDINARY
Definition pgpa_scan.h:57
Bitmapset * pgpa_filter_out_join_relids(Bitmapset *relids, List *rtable)
Bitmapset * pgpa_relids(Plan *plan)
Index pgpa_scanrelid(Plan *plan)
static int fb(int x)
NodeTag elided_type
Definition plannodes.h:1872
Bitmapset * relids
Definition plannodes.h:1873
Definition pg_list.h:54
Plan * plan
Definition pgpa_scan.h:75
pgpa_scan_strategy strategy
Definition pgpa_scan.h:76
Bitmapset * relids
Definition pgpa_scan.h:77