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 '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 *
214 rhaas@postgresql.org 44 :CBC 232203 : 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 : 232203 : pgpa_scan_strategy strategy = PGPA_SCAN_ORDINARY;
49 : 232203 : Bitmapset *relids = NULL;
50 : 232203 : int rti = -1;
51 : 232203 : List *child_append_relid_sets = NIL;
52 : 232203 : NodeTag nodetype = nodeTag(plan);
53 : :
54 [ + + ]: 232203 : if (elided_node != NULL)
55 : : {
56 : 5543 : nodetype = elided_node->elided_type;
57 : 5543 : 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 [ + + - + : 5543 : if ((nodetype == T_Append || nodetype == T_MergeAppend) &&
+ - ]
196 79 [ + + ]: 1160 : relids != NULL &&
214 80 : 1160 : unique_nonjoin_rtekind(relids,
81 : 1160 : walker->pstmt->rtable) == RTE_RELATION)
82 : 1144 : strategy = PGPA_SCAN_PARTITIONWISE;
83 : : else
84 : 4399 : strategy = PGPA_SCAN_ORDINARY;
85 : :
86 : : /* Be sure to account for pulled-up scans, as for a live Append. */
4 87 : 5543 : child_append_relid_sets = elided_node->child_append_relid_sets;
88 : :
89 : : /* Join RTIs can be present, but advice never refers to them. */
214 90 : 5543 : relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
91 : : }
92 [ + + ]: 226660 : else if ((rti = pgpa_scanrelid(plan)) != 0)
93 : : {
94 : 101296 : relids = bms_make_singleton(rti);
95 : :
96 [ + + + + : 101296 : switch (nodeTag(plan))
+ + ]
97 : : {
98 : 52530 : case T_SeqScan:
99 : 52530 : strategy = PGPA_SCAN_SEQ;
100 : 52530 : break;
101 : 4563 : case T_BitmapHeapScan:
102 : 4563 : strategy = PGPA_SCAN_BITMAP_HEAP;
103 : 4563 : break;
104 : 25868 : case T_IndexScan:
105 : 25868 : strategy = PGPA_SCAN_INDEX;
106 : 25868 : break;
107 : 3279 : case T_IndexOnlyScan:
108 : 3279 : strategy = PGPA_SCAN_INDEX_ONLY;
109 : 3279 : break;
110 : 832 : case T_TidScan:
111 : : case T_TidRangeScan:
112 : 832 : strategy = PGPA_SCAN_TID;
113 : 832 : break;
114 : 14224 : default:
115 : :
116 : : /*
117 : : * This case includes a ForeignScan targeting a single
118 : : * relation; no other strategy is possible in that case, but
119 : : * see below, where things are different in multi-relation
120 : : * cases.
121 : : */
122 : 14224 : strategy = PGPA_SCAN_ORDINARY;
123 : 14224 : break;
124 : : }
125 : : }
182 126 [ + + ]: 125364 : else if (pgpa_is_scan_level_materialize(plan))
127 : : {
128 : : /*
129 : : * Non-repeatable tablesample methods can be wrapped in a Materialize
130 : : * node that must be treated as part of the scan itself. See
131 : : * set_tablesample_rel_pathlist().
132 : : */
133 : 2 : rti = pgpa_scanrelid(plan->lefttree);
134 : 2 : relids = bms_make_singleton(rti);
135 : 2 : strategy = PGPA_SCAN_ORDINARY;
136 : : }
214 137 [ + + ]: 125362 : else if ((relids = pgpa_relids(plan)) != NULL)
138 : : {
139 [ + + + + ]: 42646 : switch (nodeTag(plan))
140 : : {
141 : 2 : case T_ForeignScan:
142 : :
143 : : /*
144 : : * If multiple relations are being targeted by a single
145 : : * foreign scan, then the foreign join has been pushed to the
146 : : * remote side, and we want that to be reflected in the
147 : : * generated advice. We can't emit FOREIGN_JOIN() advice for a
148 : : * single relation, so treat that case as an ordinary scan.
149 : : */
102 150 [ + + ]: 2 : if (bms_membership(relids) == BMS_MULTIPLE)
151 : 1 : strategy = PGPA_SCAN_FOREIGN;
152 : : else
153 : 1 : strategy = PGPA_SCAN_ORDINARY;
214 154 : 2 : break;
155 : 4944 : case T_Append:
156 : :
157 : : /*
158 : : * Append nodes can represent partitionwise scans of a
159 : : * relation, but when they implement a set operation, they are
160 : : * just ordinary scans.
161 : : */
162 [ + + ]: 4944 : if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
163 : : == RTE_RELATION)
164 : 2329 : strategy = PGPA_SCAN_PARTITIONWISE;
165 : : else
166 : 2615 : strategy = PGPA_SCAN_ORDINARY;
167 : :
168 : : /* Be sure to account for pulled-up scans. */
169 : 4944 : child_append_relid_sets =
170 : : ((Append *) plan)->child_append_relid_sets;
171 : 4944 : break;
172 : 154 : case T_MergeAppend:
173 : : /* Same logic here as for Append, above. */
174 [ + + ]: 154 : if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
175 : : == RTE_RELATION)
176 : 96 : strategy = PGPA_SCAN_PARTITIONWISE;
177 : : else
178 : 58 : strategy = PGPA_SCAN_ORDINARY;
179 : :
180 : : /* Be sure to account for pulled-up scans. */
181 : 154 : child_append_relid_sets =
182 : : ((MergeAppend *) plan)->child_append_relid_sets;
183 : 154 : break;
184 : 37546 : default:
185 : 37546 : strategy = PGPA_SCAN_ORDINARY;
186 : 37546 : break;
187 : : }
188 : :
189 : :
190 : : /* Join RTIs can be present, but advice never refers to them. */
191 : 42646 : relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
192 : : }
193 : :
194 : : /*
195 : : * If this is an Append or MergeAppend node into which subordinate Append
196 : : * or MergeAppend paths were merged, each of those merged paths is
197 : : * effectively another scan for which we need to account.
198 : : */
199 [ + + + + : 465347 : foreach_node(Bitmapset, child_relids, child_append_relid_sets)
+ + ]
200 : : {
201 : : Bitmapset *child_nonjoin_relids;
202 : :
203 : : child_nonjoin_relids =
204 : 941 : pgpa_filter_out_join_relids(child_relids,
205 : 941 : walker->pstmt->rtable);
4 206 [ + + ]: 941 : if (unique_nonjoin_rtekind(child_nonjoin_relids, walker->pstmt->rtable)
207 : : == RTE_RELATION)
208 : 919 : (void) pgpa_make_scan(walker, plan, PGPA_SCAN_PARTITIONWISE,
209 : : child_nonjoin_relids);
210 : : else
211 : 22 : (void) pgpa_make_scan(walker, plan, PGPA_SCAN_ORDINARY,
212 : : child_nonjoin_relids);
213 : : }
214 : :
215 : : /*
216 : : * If this plan node has no associated RTIs, it's not a scan. When the
217 : : * 'within_join_problem' flag is set, that's unexpected, so throw an
218 : : * error, else return quietly.
219 : : */
214 220 [ + + ]: 232203 : if (relids == NULL)
221 : : {
222 [ - + ]: 82716 : if (within_join_problem)
214 rhaas@postgresql.org 223 [ # # ]:UBC 0 : elog(ERROR, "plan node has no RTIs: %d", (int) nodeTag(plan));
214 rhaas@postgresql.org 224 :CBC 82716 : return NULL;
225 : : }
226 : :
227 : : /*
228 : : * Add the appropriate set of RTIs to walker->no_gather_scans.
229 : : *
230 : : * Add nothing if we're beneath a Gather or Gather Merge node, since
231 : : * NO_GATHER advice is clearly inappropriate in that situation.
232 : : */
3 233 [ + + ]: 149487 : if (!beneath_any_gather)
214 234 : 148155 : walker->no_gather_scans =
235 : 148155 : bms_add_members(walker->no_gather_scans, relids);
236 : :
237 : : /* Caller tells us whether NO_GATHER() advice for this scan is needed. */
238 : 149487 : return pgpa_make_scan(walker, plan, strategy, relids);
239 : : }
240 : :
241 : : /*
242 : : * Create a single pgpa_scan object and update the pgpa_plan_walker_context.
243 : : */
244 : : static pgpa_scan *
245 : 150428 : pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
246 : : pgpa_scan_strategy strategy, Bitmapset *relids)
247 : : {
248 : : pgpa_scan *scan;
249 : :
250 : : /* Create the scan object. */
56 michael@paquier.xyz 251 :GNC 150428 : scan = palloc_object(pgpa_scan);
214 rhaas@postgresql.org 252 :CBC 150428 : scan->plan = plan;
253 : 150428 : scan->strategy = strategy;
254 : 150428 : scan->relids = relids;
255 : :
256 : : /* Add it to the appropriate list. */
257 : 150428 : walker->scans[scan->strategy] = lappend(walker->scans[scan->strategy],
258 : : scan);
259 : :
260 : 150428 : return scan;
261 : : }
262 : :
263 : : /*
264 : : * Determine the unique rtekind of a set of relids.
265 : : */
266 : : static RTEKind
267 : 7199 : unique_nonjoin_rtekind(Bitmapset *relids, List *rtable)
268 : : {
269 : 7199 : int rti = -1;
270 : 7199 : bool first = true;
213 nathan@postgresql.or 271 : 7199 : RTEKind rtekind = RTE_RELATION; /* silence compiler warning */
272 : :
214 rhaas@postgresql.org 273 [ - + ]: 7199 : Assert(relids != NULL);
274 : :
275 [ + + ]: 17490 : while ((rti = bms_next_member(relids, rti)) >= 0)
276 : : {
277 : 10291 : RangeTblEntry *rte = rt_fetch(rti, rtable);
278 : :
279 [ + + ]: 10291 : if (rte->rtekind == RTE_JOIN)
280 : 182 : continue;
281 : :
282 [ + + ]: 10109 : if (first)
283 : : {
284 : 7199 : rtekind = rte->rtekind;
285 : 7199 : first = false;
286 : : }
287 [ - + ]: 2910 : else if (rtekind != rte->rtekind)
214 rhaas@postgresql.org 288 [ # # ]:UBC 0 : elog(ERROR, "rtekind mismatch: %d vs. %d",
289 : : rtekind, rte->rtekind);
290 : : }
291 : :
214 rhaas@postgresql.org 292 [ - + ]:CBC 7199 : if (first)
214 rhaas@postgresql.org 293 [ # # ]:UBC 0 : elog(ERROR, "no non-RTE_JOIN RTEs found");
294 : :
214 rhaas@postgresql.org 295 :CBC 7199 : return rtekind;
296 : : }
|