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