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 '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 : */
43 : pgpa_scan *
44 234365 : 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 234365 : pgpa_scan_strategy strategy = PGPA_SCAN_ORDINARY;
49 234365 : Bitmapset *relids = NULL;
50 234365 : int rti = -1;
51 234365 : List *child_append_relid_sets = NIL;
52 234365 : NodeTag nodetype = nodeTag(plan);
53 :
54 234365 : if (elided_node != NULL)
55 : {
56 5598 : nodetype = elided_node->elided_type;
57 5598 : 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 5598 : if ((nodetype == T_Append || nodetype == T_MergeAppend) &&
79 1188 : relids != NULL &&
80 1188 : unique_nonjoin_rtekind(relids,
81 1188 : walker->pstmt->rtable) == RTE_RELATION)
82 1176 : strategy = PGPA_SCAN_PARTITIONWISE;
83 : else
84 4422 : strategy = PGPA_SCAN_ORDINARY;
85 :
86 : /* Join RTIs can be present, but advice never refers to them. */
87 5598 : relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
88 : }
89 228767 : else if ((rti = pgpa_scanrelid(plan)) != 0)
90 : {
91 101616 : relids = bms_make_singleton(rti);
92 :
93 101616 : switch (nodeTag(plan))
94 : {
95 53754 : case T_SeqScan:
96 53754 : strategy = PGPA_SCAN_SEQ;
97 53754 : break;
98 5339 : case T_BitmapHeapScan:
99 5339 : strategy = PGPA_SCAN_BITMAP_HEAP;
100 5339 : break;
101 23729 : case T_IndexScan:
102 23729 : strategy = PGPA_SCAN_INDEX;
103 23729 : break;
104 3771 : case T_IndexOnlyScan:
105 3771 : strategy = PGPA_SCAN_INDEX_ONLY;
106 3771 : break;
107 836 : case T_TidScan:
108 : case T_TidRangeScan:
109 836 : strategy = PGPA_SCAN_TID;
110 836 : break;
111 14187 : 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 14187 : strategy = PGPA_SCAN_ORDINARY;
120 14187 : break;
121 : }
122 : }
123 127151 : else if ((relids = pgpa_relids(plan)) != NULL)
124 : {
125 42795 : switch (nodeTag(plan))
126 : {
127 0 : 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 0 : strategy = PGPA_SCAN_FOREIGN;
136 0 : break;
137 5075 : 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 5075 : if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
145 : == RTE_RELATION)
146 2439 : strategy = PGPA_SCAN_PARTITIONWISE;
147 : else
148 2636 : strategy = PGPA_SCAN_ORDINARY;
149 :
150 : /* Be sure to account for pulled-up scans. */
151 5075 : child_append_relid_sets =
152 : ((Append *) plan)->child_append_relid_sets;
153 5075 : break;
154 162 : case T_MergeAppend:
155 : /* Same logic here as for Append, above. */
156 162 : if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
157 : == RTE_RELATION)
158 96 : strategy = PGPA_SCAN_PARTITIONWISE;
159 : else
160 66 : strategy = PGPA_SCAN_ORDINARY;
161 :
162 : /* Be sure to account for pulled-up scans. */
163 162 : child_append_relid_sets =
164 : ((MergeAppend *) plan)->child_append_relid_sets;
165 162 : break;
166 37558 : default:
167 37558 : strategy = PGPA_SCAN_ORDINARY;
168 37558 : break;
169 : }
170 :
171 :
172 : /* Join RTIs can be present, but advice never refers to them. */
173 42795 : 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 469468 : foreach_node(Bitmapset, child_relids, child_append_relid_sets)
182 : {
183 : Bitmapset *child_nonjoin_relids;
184 :
185 : child_nonjoin_relids =
186 738 : pgpa_filter_out_join_relids(child_relids,
187 738 : walker->pstmt->rtable);
188 738 : (void) pgpa_make_scan(walker, plan, strategy,
189 : child_nonjoin_relids);
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 234365 : if (relids == NULL)
198 : {
199 84356 : if (within_join_problem)
200 0 : elog(ERROR, "plan node has no RTIs: %d", (int) nodeTag(plan));
201 84356 : 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 : */
214 150009 : if (!beneath_any_gather && nodetype != T_Append &&
215 : nodetype != T_MergeAppend)
216 142165 : walker->no_gather_scans =
217 142165 : bms_add_members(walker->no_gather_scans, relids);
218 :
219 : /* Caller tells us whether NO_GATHER() advice for this scan is needed. */
220 150009 : 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 : */
226 : static pgpa_scan *
227 150747 : pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
228 : pgpa_scan_strategy strategy, Bitmapset *relids)
229 : {
230 : pgpa_scan *scan;
231 :
232 : /* Create the scan object. */
233 150747 : scan = palloc(sizeof(pgpa_scan));
234 150747 : scan->plan = plan;
235 150747 : scan->strategy = strategy;
236 150747 : scan->relids = relids;
237 :
238 : /* Add it to the appropriate list. */
239 150747 : walker->scans[scan->strategy] = lappend(walker->scans[scan->strategy],
240 : scan);
241 :
242 150747 : return scan;
243 : }
244 :
245 : /*
246 : * Determine the unique rtekind of a set of relids.
247 : */
248 : static RTEKind
249 6425 : unique_nonjoin_rtekind(Bitmapset *relids, List *rtable)
250 : {
251 6425 : int rti = -1;
252 6425 : bool first = true;
253 6425 : RTEKind rtekind = RTE_RELATION; /* silence compiler warning */
254 :
255 : Assert(relids != NULL);
256 :
257 15849 : while ((rti = bms_next_member(relids, rti)) >= 0)
258 : {
259 9424 : RangeTblEntry *rte = rt_fetch(rti, rtable);
260 :
261 9424 : if (rte->rtekind == RTE_JOIN)
262 182 : continue;
263 :
264 9242 : if (first)
265 : {
266 6425 : rtekind = rte->rtekind;
267 6425 : first = false;
268 : }
269 2817 : else if (rtekind != rte->rtekind)
270 0 : elog(ERROR, "rtekind mismatch: %d vs. %d",
271 : rtekind, rte->rtekind);
272 : }
273 :
274 6425 : if (first)
275 0 : elog(ERROR, "no non-RTE_JOIN RTEs found");
276 :
277 6425 : return rtekind;
278 : }
|