Age Owner Branch data TLA Line data Source code
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 : :
20 : : static pgpa_scan *pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
21 : : pgpa_scan_strategy strategy,
22 : : Bitmapset *relids);
23 : :
24 : :
25 : : static 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 'needs_no_gather' flag if NO_GATHER advice should be generated for
37 : : * this scan.
38 : : *
39 : : * Set the 'within_join_problem' flag if we're inside of a join problem and
40 : : * not otherwise.
41 : : */
42 : : pgpa_scan *
238 rhaas@postgresql.org 43 :CBC 234046 : pgpa_build_scan(pgpa_plan_walker_context *walker, Plan *plan,
44 : : ElidedNode *elided_node,
45 : : bool needs_no_gather, bool within_join_problem)
46 : : {
47 : 234046 : pgpa_scan_strategy strategy = PGPA_SCAN_ORDINARY;
48 : 234046 : Bitmapset *relids = NULL;
49 : 234046 : int rti = -1;
50 : 234046 : List *child_append_relid_sets = NIL;
51 : 234046 : NodeTag nodetype = nodeTag(plan);
52 : :
53 [ + + ]: 234046 : if (elided_node != NULL)
54 : : {
55 : 5603 : nodetype = elided_node->elided_type;
56 : 5603 : relids = elided_node->relids;
57 : :
58 : : /*
59 : : * If setrefs processing elided an Append or MergeAppend node that had
60 : : * only one surviving child, it could be either a partitionwise
61 : : * operation or a setop over subqueries, depending on the rtekind.
62 : : *
63 : : * A setop over subqueries, or a trivial SubqueryScan that was elided,
64 : : * is an "ordinary" scan i.e. one for which we do not need to generate
65 : : * advice because the planner has not made any meaningful choice.
66 : : *
67 : : * Note that the PGPA_SCAN_PARTITIONWISE case also includes
68 : : * partitionwise joins; this module considers those to be a form of
69 : : * scan, since they lack internal structure that we can decompose.
70 : : *
71 : : * Note also that it's possible for relids to be NULL here, if the
72 : : * elided Append node is part of a partitionwise aggregate. In that
73 : : * case, it doesn't matter what strategy we choose, but we do need to
74 : : * avoid calling unique_nonjoin_rtekind(), which would fail an
75 : : * assertion.
76 : : */
77 [ + + - + : 5603 : if ((nodetype == T_Append || nodetype == T_MergeAppend) &&
+ - ]
220 78 [ + + ]: 1166 : relids != NULL &&
238 79 : 1166 : unique_nonjoin_rtekind(relids,
80 : 1166 : walker->pstmt->rtable) == RTE_RELATION)
81 : 1148 : strategy = PGPA_SCAN_PARTITIONWISE;
82 : : else
83 : 4455 : strategy = PGPA_SCAN_ORDINARY;
84 : :
85 : : /* Be sure to account for pulled-up scans, as for a live Append. */
28 86 : 5603 : child_append_relid_sets = elided_node->child_append_relid_sets;
87 : :
88 : : /* Join RTIs can be present, but advice never refers to them. */
238 89 : 5603 : relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
90 : : }
91 [ + + ]: 228443 : else if ((rti = pgpa_scanrelid(plan)) != 0)
92 : : {
93 : 102061 : relids = bms_make_singleton(rti);
94 : :
95 [ + + + + : 102061 : switch (nodeTag(plan))
+ + ]
96 : : {
97 : 52723 : case T_SeqScan:
98 : 52723 : strategy = PGPA_SCAN_SEQ;
99 : 52723 : break;
100 : 4563 : case T_BitmapHeapScan:
101 : 4563 : strategy = PGPA_SCAN_BITMAP_HEAP;
102 : 4563 : break;
103 : 26214 : case T_IndexScan:
104 : 26214 : strategy = PGPA_SCAN_INDEX;
105 : 26214 : break;
106 : 3379 : case T_IndexOnlyScan:
107 : 3379 : strategy = PGPA_SCAN_INDEX_ONLY;
108 : 3379 : break;
109 : 836 : case T_TidScan:
110 : : case T_TidRangeScan:
111 : 836 : strategy = PGPA_SCAN_TID;
112 : 836 : break;
113 : 14346 : default:
114 : :
115 : : /*
116 : : * This case includes a ForeignScan targeting a single
117 : : * relation; no other strategy is possible in that case, but
118 : : * see below, where things are different in multi-relation
119 : : * cases.
120 : : */
121 : 14346 : strategy = PGPA_SCAN_ORDINARY;
122 : 14346 : break;
123 : : }
124 : : }
206 125 [ + + ]: 126382 : else if (pgpa_is_scan_level_materialize(plan))
126 : : {
127 : : /*
128 : : * Non-repeatable tablesample methods can be wrapped in a Materialize
129 : : * node that must be treated as part of the scan itself. See
130 : : * set_tablesample_rel_pathlist().
131 : : */
132 : 2 : rti = pgpa_scanrelid(plan->lefttree);
133 : 2 : relids = bms_make_singleton(rti);
134 : 2 : strategy = PGPA_SCAN_ORDINARY;
135 : : }
238 136 [ + + ]: 126380 : else if ((relids = pgpa_relids(plan)) != NULL)
137 : : {
138 [ + + + + ]: 43057 : switch (nodeTag(plan))
139 : : {
140 : 2 : case T_ForeignScan:
141 : :
142 : : /*
143 : : * If multiple relations are being targeted by a single
144 : : * foreign scan, then the foreign join has been pushed to the
145 : : * remote side, and we want that to be reflected in the
146 : : * generated advice. We can't emit FOREIGN_JOIN() advice for a
147 : : * single relation, so treat that case as an ordinary scan.
148 : : */
126 149 [ + + ]: 2 : if (bms_membership(relids) == BMS_MULTIPLE)
150 : 1 : strategy = PGPA_SCAN_FOREIGN;
151 : : else
152 : 1 : strategy = PGPA_SCAN_ORDINARY;
238 153 : 2 : break;
154 : 4977 : case T_Append:
155 : :
156 : : /*
157 : : * Append nodes can represent partitionwise scans of a
158 : : * relation, but when they implement a set operation, they are
159 : : * just ordinary scans.
160 : : */
161 [ + + ]: 4977 : if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
162 : : == RTE_RELATION)
163 : 2343 : strategy = PGPA_SCAN_PARTITIONWISE;
164 : : else
165 : 2634 : strategy = PGPA_SCAN_ORDINARY;
166 : :
167 : : /* Be sure to account for pulled-up scans. */
168 : 4977 : child_append_relid_sets =
169 : : ((Append *) plan)->child_append_relid_sets;
170 : 4977 : break;
171 : 158 : case T_MergeAppend:
172 : : /* Same logic here as for Append, above. */
173 [ + + ]: 158 : if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
174 : : == RTE_RELATION)
175 : 100 : strategy = PGPA_SCAN_PARTITIONWISE;
176 : : else
177 : 58 : strategy = PGPA_SCAN_ORDINARY;
178 : :
179 : : /* Be sure to account for pulled-up scans. */
180 : 158 : child_append_relid_sets =
181 : : ((MergeAppend *) plan)->child_append_relid_sets;
182 : 158 : break;
183 : 37920 : default:
184 : 37920 : strategy = PGPA_SCAN_ORDINARY;
185 : 37920 : break;
186 : : }
187 : :
188 : :
189 : : /* Join RTIs can be present, but advice never refers to them. */
190 : 43057 : relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
191 : : }
192 : :
193 : : /*
194 : : * If this is an Append or MergeAppend node into which subordinate Append
195 : : * or MergeAppend paths were merged, each of those merged paths is
196 : : * effectively another scan for which we need to account.
197 : : */
198 [ + + + + : 469027 : foreach_node(Bitmapset, child_relids, child_append_relid_sets)
+ + ]
199 : : {
200 : : Bitmapset *child_nonjoin_relids;
201 : :
202 : : child_nonjoin_relids =
203 : 935 : pgpa_filter_out_join_relids(child_relids,
204 : 935 : walker->pstmt->rtable);
28 205 [ + + ]: 935 : if (unique_nonjoin_rtekind(child_nonjoin_relids, walker->pstmt->rtable)
206 : : == RTE_RELATION)
207 : 913 : (void) pgpa_make_scan(walker, plan, PGPA_SCAN_PARTITIONWISE,
208 : : child_nonjoin_relids);
209 : : else
210 : 22 : (void) pgpa_make_scan(walker, plan, PGPA_SCAN_ORDINARY,
211 : : child_nonjoin_relids);
212 : : }
213 : :
214 : : /*
215 : : * If this plan node has no associated RTIs, it's not a scan. When the
216 : : * 'within_join_problem' flag is set, that's unexpected, so throw an
217 : : * error, else return quietly.
218 : : */
238 219 [ + + ]: 234046 : if (relids == NULL)
220 : : {
221 [ - + ]: 83323 : if (within_join_problem)
238 rhaas@postgresql.org 222 [ # # ]:UBC 0 : elog(ERROR, "plan node has no RTIs: %d", (int) nodeTag(plan));
238 rhaas@postgresql.org 223 :CBC 83323 : return NULL;
224 : : }
225 : :
226 : : /* Caller tells us whether NO_GATHER() advice for this scan is needed. */
7 227 [ + + ]: 150723 : if (needs_no_gather)
238 228 : 143766 : walker->no_gather_scans =
229 : 143766 : bms_add_members(walker->no_gather_scans, relids);
230 : :
231 : : /* Now create and return the final pgpa_scan object. */
232 : 150723 : return pgpa_make_scan(walker, plan, strategy, relids);
233 : : }
234 : :
235 : : /*
236 : : * Create a single pgpa_scan object and update the pgpa_plan_walker_context.
237 : : */
238 : : static pgpa_scan *
239 : 151658 : pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
240 : : pgpa_scan_strategy strategy, Bitmapset *relids)
241 : : {
242 : : pgpa_scan *scan;
243 : :
244 : : /* Create the scan object. */
80 michael@paquier.xyz 245 :GNC 151658 : scan = palloc_object(pgpa_scan);
238 rhaas@postgresql.org 246 :CBC 151658 : scan->plan = plan;
247 : 151658 : scan->strategy = strategy;
248 : 151658 : scan->relids = relids;
249 : :
250 : : /* Add it to the appropriate list. */
251 : 151658 : walker->scans[scan->strategy] = lappend(walker->scans[scan->strategy],
252 : : scan);
253 : :
254 : 151658 : return scan;
255 : : }
256 : :
257 : : /*
258 : : * Does this Plan node or associated ElidedNode look like the topmost node
259 : : * generated while planning an unflattened set operation?
260 : : *
261 : : * The return value of this function is a little bit inexact, because for
262 : : * example if a set operation produces a HashAggregate on top of an Append,
263 : : * this function will only identify the latter. Since the HashAggregate
264 : : * node is not RTI-bearing, the difference doesn't currently matter.
265 : : *
266 : : * Pass the plan node as 'plan', and if we're considering an elided node
267 : : * associated with that plan node, also pass it as 'elided_node'.
268 : : */
269 : : bool
7 270 : 251469 : pgpa_is_setop_parent(Plan *plan, ElidedNode *elided_node, List *rtable)
271 : : {
272 : : Bitmapset *relids;
273 : 251469 : int rti = -1;
274 : :
275 : : /*
276 : : * If we're considering a SetOp or RecursiveUnion, then this is definitely
277 : : * a setop parent. If we're considering an Append, a MergeAppend, or an
278 : : * upper Result node, this might be a setop parent. If it's anything else,
279 : : * this is not a setop parent.
280 : : */
281 [ + + ]: 251469 : if (elided_node != NULL)
282 : : {
283 [ + + ]: 3291 : if (elided_node->elided_type == T_Append ||
284 [ - + ]: 2125 : elided_node->elided_type == T_MergeAppend)
285 : 1166 : relids = elided_node->relids;
286 : : else
287 : 2125 : return false;
288 : : }
289 : : else
290 : : {
291 [ + + + + ]: 248178 : if (IsA(plan, SetOp) || IsA(plan, RecursiveUnion))
292 : 378 : return true;
293 [ + + + + ]: 247800 : else if (IsA(plan, Append) || IsA(plan, MergeAppend) ||
294 [ + + ]: 242505 : (IsA(plan, Result) &&
295 [ + + ]: 41689 : (((Result *) plan)->result_type == RESULT_TYPE_UPPER)))
296 : 5316 : relids = pgpa_relids(plan);
297 : : else
298 : 242484 : return false;
299 : : }
300 : :
301 : : /*
302 : : * As of this writing, we reach here with relids == NULL only for a
303 : : * partitionwise aggregate, which is not a setop parent.
304 : : */
305 [ + + ]: 6482 : if (relids == NULL)
306 : 203 : return false;
307 : :
308 : : /*
309 : : * Conceptually, this Append or MergeAppend, or the Result node that
310 : : * replaced it, could be a partitionwise scan, a partitionwise join, a
311 : : * flattened UNION ALL, or an unflattened set operation. We want to return
312 : : * true only in that last case; for our purposes, if a UNION ALL is
313 : : * flattened, it no longer counts as a set operation.
314 : : *
315 : : * An unflattened set operation has no RTI of its own, so a plan node
316 : : * implementing one carries the RTIs of its children, which must always be
317 : : * subqueries with rte->inh == false. A flattened set operation does have
318 : : * an RTI, and that one has rte->inh == true.
319 : : */
320 [ + + ]: 9960 : while ((rti = bms_next_member(relids, rti)) >= 0)
321 : : {
322 : 8662 : RangeTblEntry *rte = rt_fetch(rti, rtable);
323 : :
324 [ + + + + ]: 8662 : if (rte->rtekind != RTE_SUBQUERY || rte->inh)
325 : 4981 : return false;
326 : : }
327 : :
328 : 1298 : return true;
329 : : }
330 : :
331 : : /*
332 : : * Determine the unique rtekind of a set of relids.
333 : : */
334 : : static RTEKind
238 335 : 7236 : unique_nonjoin_rtekind(Bitmapset *relids, List *rtable)
336 : : {
337 : 7236 : int rti = -1;
338 : 7236 : bool first = true;
237 nathan@postgresql.or 339 : 7236 : RTEKind rtekind = RTE_RELATION; /* silence compiler warning */
340 : :
238 rhaas@postgresql.org 341 [ - + ]: 7236 : Assert(relids != NULL);
342 : :
343 [ + + ]: 17616 : while ((rti = bms_next_member(relids, rti)) >= 0)
344 : : {
345 : 10380 : RangeTblEntry *rte = rt_fetch(rti, rtable);
346 : :
347 [ + + ]: 10380 : if (rte->rtekind == RTE_JOIN)
348 : 214 : continue;
349 : :
350 [ + + ]: 10166 : if (first)
351 : : {
352 : 7236 : rtekind = rte->rtekind;
353 : 7236 : first = false;
354 : : }
355 [ - + ]: 2930 : else if (rtekind != rte->rtekind)
238 rhaas@postgresql.org 356 [ # # ]:UBC 0 : elog(ERROR, "rtekind mismatch: %d vs. %d",
357 : : rtekind, rte->rtekind);
358 : : }
359 : :
238 rhaas@postgresql.org 360 [ - + ]:CBC 7236 : if (first)
238 rhaas@postgresql.org 361 [ # # ]:UBC 0 : elog(ERROR, "no non-RTE_JOIN RTEs found");
362 : :
238 rhaas@postgresql.org 363 :CBC 7236 : return rtekind;
364 : : }
|