Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pgpa_scan.c
4 : : * analysis of scans in Plan trees
5 : : *
6 : : * Copyright (c) 2016-2025, 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 : :
20 : : static pgpa_scan *pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
21 : : pgpa_scan_strategy strategy,
22 : : Bitmapset *relids,
23 : : bool needs_no_gather);
24 : :
25 : :
26 : : static RTEKind unique_nonjoin_rtekind(Bitmapset *relids, List *rtable);
27 : :
28 : : /*
29 : : * Build a pgpa_scan object for a Plan node and update the plan walker
30 : : * context as appopriate. If this is an Append or MergeAppend scan, also
31 : : * build pgpa_scan for any scans that were consolidated into this one by
32 : : * Append/MergeAppend pull-up.
33 : : *
34 : : * If there is at least one ElidedNode for this plan node, pass the uppermost
35 : : * one as elided_node, else pass NULL.
36 : : *
37 : : * Set the 'beneath_any_gather' node if we are underneath a Gather or
38 : : * Gather Merge node.
39 : : *
40 : : * Set the 'within_join_problem' flag if we're inside of a join problem and
41 : : * not otherwise.
42 : : */
43 : : pgpa_scan *
44 : 111941 : pgpa_build_scan(pgpa_plan_walker_context *walker, Plan *plan,
45 : : ElidedNode *elided_node,
46 : : bool beneath_any_gather, bool within_join_problem)
47 : : {
48 : 111941 : pgpa_scan_strategy strategy = PGPA_SCAN_ORDINARY;
49 : 111941 : Bitmapset *relids = NULL;
50 : 111941 : int rti = -1;
51 : 111941 : bool needs_no_gather = !beneath_any_gather;
52 : 111941 : List *child_append_relid_sets = NIL;
53 : :
54 [ + + ]: 111941 : if (elided_node != NULL)
55 : : {
56 : 2645 : NodeTag elided_type = elided_node->elided_type;
57 : :
58 : : /*
59 : : * If setrefs processing elided an Append or MergeAppend node that had
60 : : * only one surviving child, it might be a partitionwise operation,
61 : : * but then this is either a setop over subqueries, or a partitionwise
62 : : * operation (which might be a scan or a join in reality, but here we
63 : : * don't care about the distinction and consider it simply a scan).
64 : : *
65 : : * A setop over subqueries, or a trivial SubQueryScan that was elided,
66 : : * is an "ordinary" scan i.e. one for which we need to generate advice
67 : : * because the planner has not made any meaningful choice.
68 : : */
69 : 2645 : relids = elided_node->relids;
70 [ + + + + ]: 2645 : if ((elided_type == T_Append || elided_type == T_MergeAppend) &&
71 : 5290 : unique_nonjoin_rtekind(relids,
72 : 5290 : walker->pstmt->rtable) == RTE_RELATION)
73 : 567 : strategy = PGPA_SCAN_PARTITIONWISE;
74 : : else
75 : 2078 : strategy = PGPA_SCAN_ORDINARY;
76 : :
77 : : /*
78 : : * If this is an elided Append or MergeAppend node, we don't need to
79 : : * emit NO_GATHER() because we'll also emit it for the underlying
80 : : * scan, which is good enough.
81 : : *
82 : : * If it's an elided SubqueryScan, the same argument is likely to
83 : : * apply, because the subquery probably contains references to tables,
84 : : * and if it doesn't, then planner isn't likely to want to do it in
85 : : * parallel anyway. But also, we can't implement NO_GATHER() advice
86 : : * for a non-relation RTEKind, because get_relation_info() isn't
87 : : * called in such cases. That should probably be fixed at some point,
88 : : * but until then we shouldn't emit it.
89 : : */
90 : 2645 : needs_no_gather = false;
91 : :
92 : : /* Join RTIs can be present, but advice never refers to them. */
93 : 2645 : relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
94 : 2645 : }
95 [ + + ]: 109296 : else if ((rti = pgpa_scanrelid(plan)) != 0)
96 : : {
97 : 49282 : RangeTblEntry *rte;
98 : :
99 : 49282 : relids = bms_make_singleton(rti);
100 : :
101 [ + + + + : 49282 : switch (nodeTag(plan))
+ + ]
102 : : {
103 : : case T_SeqScan:
104 : 25934 : strategy = PGPA_SCAN_SEQ;
105 : 25934 : break;
106 : : case T_BitmapHeapScan:
107 : 2656 : strategy = PGPA_SCAN_BITMAP_HEAP;
108 : 2656 : break;
109 : : case T_IndexScan:
110 : 11684 : strategy = PGPA_SCAN_INDEX;
111 : 11684 : break;
112 : : case T_IndexOnlyScan:
113 : 1841 : strategy = PGPA_SCAN_INDEX_ONLY;
114 : 1841 : break;
115 : : case T_TidScan:
116 : : case T_TidRangeScan:
117 : 421 : strategy = PGPA_SCAN_TID;
118 : 421 : break;
119 : : default:
120 : :
121 : : /*
122 : : * This case includes a ForeignScan targeting a single
123 : : * relation; no other strategy is possible in that case, but
124 : : * see below, where things are different in multi-relation
125 : : * cases.
126 : : */
127 : 6746 : strategy = PGPA_SCAN_ORDINARY;
128 : :
129 : : /*
130 : : * We can't handle NO_GATHER() for non-relation RTEs because
131 : : * get_relation_info won't be called.
132 : : */
133 : 6746 : rte = rt_fetch(rti, walker->pstmt->rtable);
134 [ + + ]: 6746 : if (rte->rtekind != RTE_RELATION)
135 : 6701 : needs_no_gather = false;
136 : :
137 : 6746 : break;
138 : : }
139 : 49282 : }
140 [ + + ]: 60014 : else if ((relids = pgpa_relids(plan)) != NULL)
141 : : {
142 [ + - + + ]: 20333 : switch (nodeTag(plan))
143 : : {
144 : : case T_ForeignScan:
145 : :
146 : : /*
147 : : * If multiple relations are being targeted by a single
148 : : * foreign scan, then the foreign join has been pushed to the
149 : : * remote side, and we want that to be reflected in the
150 : : * generated advice.
151 : : */
152 : 0 : strategy = PGPA_SCAN_FOREIGN;
153 : 0 : break;
154 : : case T_Append:
155 : :
156 : : /*
157 : : * Append nodes can represent partitionwise scans of a a
158 : : * relation, but when they implement a set operation, they are
159 : : * just ordinary scans.
160 : : */
161 : 2434 : if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
162 [ + + ]: 2434 : == RTE_RELATION)
163 : 1177 : strategy = PGPA_SCAN_PARTITIONWISE;
164 : : else
165 : 1257 : strategy = PGPA_SCAN_ORDINARY;
166 : :
167 : : /* NO_GATHER() should be emitted for underlying rels only. */
168 : 2434 : needs_no_gather = false;
169 : :
170 : : /* Be sure to account for pulled-up scans. */
171 : 2434 : child_append_relid_sets =
172 : 2434 : ((Append *) plan)->child_append_relid_sets;
173 : 2434 : break;
174 : : case T_MergeAppend:
175 : : /* Some logic here as for Append, above. */
176 : 79 : if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
177 [ + + ]: 79 : == RTE_RELATION)
178 : 46 : strategy = PGPA_SCAN_PARTITIONWISE;
179 : : else
180 : 33 : strategy = PGPA_SCAN_ORDINARY;
181 : :
182 : : /* NO_GATHER() should be emitted for underlying rels only. */
183 : 79 : needs_no_gather = false;
184 : :
185 : : /* Be sure to account for pulled-up scans. */
186 : 79 : child_append_relid_sets =
187 : 79 : ((MergeAppend *) plan)->child_append_relid_sets;
188 : 79 : break;
189 : : default:
190 : 17820 : strategy = PGPA_SCAN_ORDINARY;
191 : :
192 : : /*
193 : : * We can't handle NO_GATHER() for single non-relation RTEs
194 : : * because get_relation_info won't be called.
195 : : */
196 [ + + ]: 17820 : if (bms_membership(relids) == BMS_SINGLETON)
197 : : {
198 : 17787 : RangeTblEntry *rte;
199 : :
200 : 17787 : rte = rt_fetch(bms_singleton_member(relids),
201 : : walker->pstmt->rtable);
202 [ + + ]: 17787 : if (rte->rtekind != RTE_RELATION)
203 : 17667 : needs_no_gather = false;
204 : 17787 : }
205 : :
206 : 17820 : break;
207 : : }
208 : :
209 : :
210 : : /* Join RTIs can be present, but advice never refers to them. */
211 : 20333 : relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
212 : 20333 : }
213 : :
214 : : /*
215 : : * If this is an Append or MergeAppend node into which subordinate Append
216 : : * or MergeAppend paths were merged, each of those merged paths is
217 : : * effectively another scan for which we need to account.
218 : : */
219 [ + + + + : 224248 : foreach_node(Bitmapset, child_relids, child_append_relid_sets)
+ + + + ]
220 : : {
221 : 366 : Bitmapset *child_nonjoin_relids;
222 : :
223 : 366 : child_nonjoin_relids =
224 : 732 : pgpa_filter_out_join_relids(child_relids,
225 : 366 : walker->pstmt->rtable);
226 : 732 : (void) pgpa_make_scan(walker, plan, strategy,
227 : 366 : child_nonjoin_relids, false);
228 : 112307 : }
229 : :
230 : : /*
231 : : * If this plan node has no associated RTIs, it's not a scan. When the
232 : : * 'within_join_problem' flag is set, that's unexpected, so throw an
233 : : * error, else return quietly.
234 : : */
235 [ + + ]: 111941 : if (relids == NULL)
236 : : {
237 [ + - ]: 39681 : if (within_join_problem)
238 [ # # # # ]: 0 : elog(ERROR, "plan node has no RTIs: %d", (int) nodeTag(plan));
239 : 39681 : return NULL;
240 : : }
241 : :
242 : 72260 : return pgpa_make_scan(walker, plan, strategy, relids, needs_no_gather);
243 : 111941 : }
244 : :
245 : : /*
246 : : * Create a single pgpa_scan object and update the pgpa_plan_walker_context.
247 : : */
248 : : static pgpa_scan *
249 : 72626 : pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
250 : : pgpa_scan_strategy strategy, Bitmapset *relids,
251 : : bool needs_no_gather)
252 : : {
253 : 72626 : pgpa_scan *scan;
254 : :
255 : : /* Create the scan object. */
256 : 72626 : scan = palloc(sizeof(pgpa_scan));
257 : 72626 : scan->plan = plan;
258 : 72626 : scan->strategy = strategy;
259 : 72626 : scan->relids = relids;
260 : :
261 : : /* Add it to the appropriate list. */
262 : 145252 : walker->scans[scan->strategy] = lappend(walker->scans[scan->strategy],
263 : 72626 : scan);
264 : :
265 : : /* Caller tells us whether NO_GATHER() advice for this scan is needed. */
266 [ + + ]: 72626 : if (needs_no_gather)
267 : 84478 : walker->no_gather_scans = bms_add_members(walker->no_gather_scans,
268 : 42239 : scan->relids);
269 : :
270 : 145252 : return scan;
271 : 72626 : }
272 : :
273 : : /*
274 : : * Determine the unique rtekind of a set of relids.
275 : : */
276 : : static RTEKind
277 : 3085 : unique_nonjoin_rtekind(Bitmapset *relids, List *rtable)
278 : : {
279 : 3085 : int rti = -1;
280 : 3085 : bool first = true;
281 : 3085 : RTEKind rtekind;
282 : :
283 [ + - ]: 3085 : Assert(relids != NULL);
284 : :
285 [ + + ]: 7657 : while ((rti = bms_next_member(relids, rti)) >= 0)
286 : : {
287 : 4572 : RangeTblEntry *rte = rt_fetch(rti, rtable);
288 : :
289 [ + + ]: 4572 : if (rte->rtekind == RTE_JOIN)
290 : 89 : continue;
291 : :
292 [ + + ]: 4483 : if (first)
293 : : {
294 : 3085 : rtekind = rte->rtekind;
295 : 3085 : first = false;
296 : 3085 : }
297 [ + - ]: 1398 : else if (rtekind != rte->rtekind)
298 [ # # # # ]: 0 : elog(ERROR, "rtekind mismatch: %d vs. %d",
299 : : rtekind, rte->rtekind);
300 [ - + + ]: 4572 : }
301 : :
302 [ + - ]: 3085 : if (first)
303 [ # # # # ]: 0 : elog(ERROR, "no non-RTE_JOIN RTEs found");
304 : :
305 : 6170 : return rtekind;
306 : 3085 : }
|