Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pgpa_ast.c
4 : : * additional supporting code related to plan advice parsing
5 : : *
6 : : * Copyright (c) 2016-2026, PostgreSQL Global Development Group
7 : : *
8 : : * contrib/pg_plan_advice/pgpa_ast.c
9 : : *
10 : : *-------------------------------------------------------------------------
11 : : */
12 : :
13 : : #include "postgres.h"
14 : :
15 : : #include "pgpa_ast.h"
16 : :
17 : : #include "funcapi.h"
18 : : #include "utils/array.h"
19 : : #include "utils/builtins.h"
20 : :
21 : : static bool pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
22 : : pgpa_advice_target *target,
23 : : bool *rids_used);
24 : :
25 : : /*
26 : : * Get a C string that corresponds to the specified advice tag.
27 : : */
28 : : char *
79 rhaas@postgresql.org 29 :GNC 144647 : pgpa_cstring_advice_tag(pgpa_advice_tag_type advice_tag)
30 : : {
31 : 144647 : switch (advice_tag)
[ + + + +
+ + + + +
+ + + + +
+ + + + +
+ - ]
32 : : {
33 : 2416 : case PGPA_TAG_BITMAP_HEAP_SCAN:
34 : 2416 : return "BITMAP_HEAP_SCAN";
65 35 : 430 : case PGPA_TAG_DO_NOT_SCAN:
36 : 430 : return "DO_NOT_SCAN";
79 37 : 1 : case PGPA_TAG_FOREIGN_JOIN:
38 : 1 : return "FOREIGN_JOIN";
39 : 173 : case PGPA_TAG_GATHER:
40 : 173 : return "GATHER";
41 : 60 : case PGPA_TAG_GATHER_MERGE:
42 : 60 : return "GATHER_MERGE";
43 : 4284 : case PGPA_TAG_HASH_JOIN:
44 : 4284 : return "HASH_JOIN";
45 : 1918 : case PGPA_TAG_INDEX_ONLY_SCAN:
46 : 1918 : return "INDEX_ONLY_SCAN";
47 : 11934 : case PGPA_TAG_INDEX_SCAN:
48 : 11934 : return "INDEX_SCAN";
49 : 11174 : case PGPA_TAG_JOIN_ORDER:
50 : 11174 : return "JOIN_ORDER";
51 : 27 : case PGPA_TAG_MERGE_JOIN_MATERIALIZE:
52 : 27 : return "MERGE_JOIN_MATERIALIZE";
53 : 551 : case PGPA_TAG_MERGE_JOIN_PLAIN:
54 : 551 : return "MERGE_JOIN_PLAIN";
55 : 514 : case PGPA_TAG_NESTED_LOOP_MATERIALIZE:
56 : 514 : return "NESTED_LOOP_MATERIALIZE";
57 : 232 : case PGPA_TAG_NESTED_LOOP_MEMOIZE:
58 : 232 : return "NESTED_LOOP_MEMOIZE";
59 : 9238 : case PGPA_TAG_NESTED_LOOP_PLAIN:
60 : 9238 : return "NESTED_LOOP_PLAIN";
61 : 71260 : case PGPA_TAG_NO_GATHER:
62 : 71260 : return "NO_GATHER";
63 : 2182 : case PGPA_TAG_PARTITIONWISE:
64 : 2182 : return "PARTITIONWISE";
65 : 626 : case PGPA_TAG_SEMIJOIN_NON_UNIQUE:
66 : 626 : return "SEMIJOIN_NON_UNIQUE";
67 : 81 : case PGPA_TAG_SEMIJOIN_UNIQUE:
68 : 81 : return "SEMIJOIN_UNIQUE";
69 : 27124 : case PGPA_TAG_SEQ_SCAN:
70 : 27124 : return "SEQ_SCAN";
71 : 422 : case PGPA_TAG_TID_SCAN:
72 : 422 : return "TID_SCAN";
73 : : }
74 : :
79 rhaas@postgresql.org 75 :UNC 0 : pg_unreachable();
76 : : return NULL;
77 : : }
78 : :
79 : : /*
80 : : * Convert an advice tag, formatted as a string that has already been
81 : : * downcased as appropriate, to a pgpa_advice_tag_type.
82 : : *
83 : : * If we succeed, set *fail = false and return the result; if we fail,
84 : : * set *fail = true and return an arbitrary value.
85 : : */
86 : : pgpa_advice_tag_type
79 rhaas@postgresql.org 87 :GNC 341645 : pgpa_parse_advice_tag(const char *tag, bool *fail)
88 : : {
89 : 341645 : *fail = false;
90 : :
91 : 341645 : switch (tag[0])
[ + + + +
+ + + + +
+ + + + ]
92 : : {
93 : 8974 : case 'b':
94 [ + + ]: 8974 : if (strcmp(tag, "bitmap_heap_scan") == 0)
95 : 2311 : return PGPA_TAG_BITMAP_HEAP_SCAN;
96 : 6663 : break;
65 97 : 3085 : case 'd':
98 [ + + ]: 3085 : if (strcmp(tag, "do_not_scan") == 0)
99 : 232 : return PGPA_TAG_DO_NOT_SCAN;
100 : 2853 : break;
79 101 : 5353 : case 'f':
102 [ + + ]: 5353 : if (strcmp(tag, "foreign_join") == 0)
103 : 8 : return PGPA_TAG_FOREIGN_JOIN;
104 : 5345 : break;
105 : 2532 : case 'g':
106 [ + + ]: 2532 : if (strcmp(tag, "gather") == 0)
107 : 341 : return PGPA_TAG_GATHER;
108 [ + + ]: 2191 : if (strcmp(tag, "gather_merge") == 0)
109 : 116 : return PGPA_TAG_GATHER_MERGE;
110 : 2075 : break;
111 : 6467 : case 'h':
112 [ + + ]: 6467 : if (strcmp(tag, "hash_join") == 0)
113 : 5827 : return PGPA_TAG_HASH_JOIN;
114 : 640 : break;
115 : 17037 : case 'i':
116 [ + + ]: 17037 : if (strcmp(tag, "index_scan") == 0)
117 : 7252 : return PGPA_TAG_INDEX_SCAN;
118 [ + + ]: 9785 : if (strcmp(tag, "index_only_scan") == 0)
119 : 1708 : return PGPA_TAG_INDEX_ONLY_SCAN;
120 : 8077 : break;
121 : 11869 : case 'j':
122 [ + + ]: 11869 : if (strcmp(tag, "join_order") == 0)
123 : 11197 : return PGPA_TAG_JOIN_ORDER;
124 : 672 : break;
125 : 4274 : case 'm':
126 [ + + ]: 4274 : if (strcmp(tag, "merge_join_materialize") == 0)
127 : 58 : return PGPA_TAG_MERGE_JOIN_MATERIALIZE;
128 [ + + ]: 4216 : if (strcmp(tag, "merge_join_plain") == 0)
129 : 924 : return PGPA_TAG_MERGE_JOIN_PLAIN;
130 : 3292 : break;
131 : 63098 : case 'n':
132 [ + + ]: 63098 : if (strcmp(tag, "nested_loop_materialize") == 0)
133 : 938 : return PGPA_TAG_NESTED_LOOP_MATERIALIZE;
134 [ + + ]: 62160 : if (strcmp(tag, "nested_loop_memoize") == 0)
135 : 440 : return PGPA_TAG_NESTED_LOOP_MEMOIZE;
136 [ + + ]: 61720 : if (strcmp(tag, "nested_loop_plain") == 0)
137 : 11922 : return PGPA_TAG_NESTED_LOOP_PLAIN;
138 [ + + ]: 49798 : if (strcmp(tag, "no_gather") == 0)
139 : 43404 : return PGPA_TAG_NO_GATHER;
140 : 6394 : break;
141 : 84018 : case 'p':
142 [ + + ]: 84018 : if (strcmp(tag, "partitionwise") == 0)
143 : 3348 : return PGPA_TAG_PARTITIONWISE;
144 : 80670 : break;
145 : 39921 : case 's':
146 [ + + ]: 39921 : if (strcmp(tag, "semijoin_non_unique") == 0)
147 : 1182 : return PGPA_TAG_SEMIJOIN_NON_UNIQUE;
148 [ + + ]: 38739 : if (strcmp(tag, "semijoin_unique") == 0)
149 : 154 : return PGPA_TAG_SEMIJOIN_UNIQUE;
150 [ + + ]: 38585 : if (strcmp(tag, "seq_scan") == 0)
151 : 16621 : return PGPA_TAG_SEQ_SCAN;
152 : 21964 : break;
153 : 23882 : case 't':
154 [ + + ]: 23882 : if (strcmp(tag, "tid_scan") == 0)
155 : 405 : return PGPA_TAG_TID_SCAN;
156 : 23477 : break;
157 : : }
158 : :
159 : : /* didn't work out */
160 : 233257 : *fail = true;
161 : :
162 : : /* return an arbitrary value to unwind the call stack */
163 : 233257 : return PGPA_TAG_SEQ_SCAN;
164 : : }
165 : :
166 : : /*
167 : : * Format a pgpa_advice_target as a string and append result to a StringInfo.
168 : : */
169 : : void
170 : 173136 : pgpa_format_advice_target(StringInfo str, pgpa_advice_target *target)
171 : : {
172 [ + + ]: 173136 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
173 : : {
174 : 12557 : bool first = true;
175 : : char *delims;
176 : :
177 [ + + ]: 12557 : if (target->ttype == PGPA_TARGET_UNORDERED_LIST)
178 : 14 : delims = "{}";
179 : : else
180 : 12543 : delims = "()";
181 : :
182 : 12557 : appendStringInfoChar(str, delims[0]);
183 [ + - + + : 53603 : foreach_ptr(pgpa_advice_target, child_target, target->children)
+ + ]
184 : : {
185 [ + + ]: 28489 : if (first)
186 : 12557 : first = false;
187 : : else
188 : 15932 : appendStringInfoChar(str, ' ');
189 : 28489 : pgpa_format_advice_target(str, child_target);
190 : : }
191 : 12557 : appendStringInfoChar(str, delims[1]);
192 : : }
193 : : else
194 : : {
195 : : const char *rt_identifier;
196 : :
197 : 160579 : rt_identifier = pgpa_identifier_string(&target->rid);
198 : 160579 : appendStringInfoString(str, rt_identifier);
199 : : }
200 : 173136 : }
201 : :
202 : : /*
203 : : * Format a pgpa_index_target as a string and append result to a StringInfo.
204 : : */
205 : : void
206 : 13852 : pgpa_format_index_target(StringInfo str, pgpa_index_target *itarget)
207 : : {
208 [ + + ]: 13852 : if (itarget->indnamespace != NULL)
209 : 13834 : appendStringInfo(str, "%s.",
210 : 13834 : quote_identifier(itarget->indnamespace));
211 : 13852 : appendStringInfoString(str, quote_identifier(itarget->indname));
212 : 13852 : }
213 : :
214 : : /*
215 : : * Determine whether two pgpa_index_target objects are exactly identical.
216 : : */
217 : : bool
218 : 2 : pgpa_index_targets_equal(pgpa_index_target *i1, pgpa_index_target *i2)
219 : : {
220 : : /* indnamespace can be NULL, and two NULL values are equal */
221 [ + - + + ]: 2 : if ((i1->indnamespace != NULL || i2->indnamespace != NULL) &&
222 [ - + - - ]: 1 : (i1->indnamespace == NULL || i2->indnamespace == NULL ||
79 rhaas@postgresql.org 223 [ # # ]:UNC 0 : strcmp(i1->indnamespace, i2->indnamespace) != 0))
79 rhaas@postgresql.org 224 :GNC 1 : return false;
225 [ - + ]: 1 : if (strcmp(i1->indname, i2->indname) != 0)
79 rhaas@postgresql.org 226 :UNC 0 : return false;
227 : :
79 rhaas@postgresql.org 228 :GNC 1 : return true;
229 : : }
230 : :
231 : : /*
232 : : * Check whether an identifier matches an any part of an advice target.
233 : : */
234 : : bool
235 : 1483435 : pgpa_identifier_matches_target(pgpa_identifier *rid, pgpa_advice_target *target)
236 : : {
237 : : /* For non-identifiers, check all descendants. */
238 [ + + ]: 1483435 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
239 : : {
240 [ + - + + : 160955 : foreach_ptr(pgpa_advice_target, child_target, target->children)
+ + ]
241 : : {
242 [ + + ]: 153799 : if (pgpa_identifier_matches_target(rid, child_target))
243 : 74830 : return true;
244 : : }
245 : 3578 : return false;
246 : : }
247 : :
248 : : /* Straightforward comparisons of alias name and occurrence number. */
249 [ + + ]: 1405027 : if (strcmp(rid->alias_name, target->rid.alias_name) != 0)
250 : 800566 : return false;
251 [ + + ]: 604461 : if (rid->occurrence != target->rid.occurrence)
252 : 27158 : return false;
253 : :
254 : : /*
255 : : * If a relation identifier mentions a partition name, it should also
256 : : * specify a partition schema. But the target may leave the schema NULL to
257 : : * match anything.
258 : : */
259 [ + + - + ]: 577303 : Assert(rid->partnsp != NULL || rid->partrel == NULL);
260 [ + + + + ]: 577303 : if (rid->partnsp != NULL && target->rid.partnsp != NULL &&
261 [ - + ]: 48231 : strcmp(rid->partnsp, target->rid.partnsp) != 0)
79 rhaas@postgresql.org 262 :UNC 0 : return false;
263 : :
264 : : /*
265 : : * These fields can be NULL on either side, but NULL only matches another
266 : : * NULL.
267 : : */
79 rhaas@postgresql.org 268 [ + + ]:GNC 577303 : if (!strings_equal_or_both_null(rid->partrel, target->rid.partrel))
269 : 11 : return false;
270 [ - + ]: 577292 : if (!strings_equal_or_both_null(rid->plan_name, target->rid.plan_name))
79 rhaas@postgresql.org 271 :UNC 0 : return false;
272 : :
79 rhaas@postgresql.org 273 :GNC 577292 : return true;
274 : : }
275 : :
276 : : /*
277 : : * Match identifiers to advice targets and return an enum value indicating
278 : : * the relationship between the set of keys and the set of targets.
279 : : *
280 : : * See the comments for pgpa_itm_type.
281 : : */
282 : : pgpa_itm_type
283 : 363340 : pgpa_identifiers_match_target(int nrids, pgpa_identifier *rids,
284 : : pgpa_advice_target *target)
285 : : {
286 : 363340 : bool all_rids_used = true;
287 : 363340 : bool any_rids_used = false;
288 : : bool all_targets_used;
289 : 363340 : bool *rids_used = palloc0_array(bool, nrids);
290 : :
291 : : all_targets_used =
292 : 363340 : pgpa_identifiers_cover_target(nrids, rids, target, rids_used);
293 : :
294 [ + + ]: 1023285 : for (int i = 0; i < nrids; ++i)
295 : : {
296 [ + + ]: 659945 : if (rids_used[i])
297 : 304492 : any_rids_used = true;
298 : : else
299 : 355453 : all_rids_used = false;
300 : : }
301 : :
302 [ + + ]: 363340 : if (all_rids_used)
303 : : {
304 [ + + ]: 139483 : if (all_targets_used)
305 : 121216 : return PGPA_ITM_EQUAL;
306 : : else
307 : 18267 : return PGPA_ITM_KEYS_ARE_SUBSET;
308 : : }
309 : : else
310 : : {
311 [ + + ]: 223857 : if (all_targets_used)
312 : 85457 : return PGPA_ITM_TARGETS_ARE_SUBSET;
313 [ + + ]: 138400 : else if (any_rids_used)
314 : 21156 : return PGPA_ITM_INTERSECTING;
315 : : else
316 : 117244 : return PGPA_ITM_DISJOINT;
317 : : }
318 : : }
319 : :
320 : : /*
321 : : * Returns true if every target or sub-target is matched by at least one
322 : : * identifier, and otherwise false.
323 : : *
324 : : * Also sets rids_used[i] = true for each identifier that matches at least one
325 : : * target.
326 : : */
327 : : static bool
328 : 658546 : pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
329 : : pgpa_advice_target *target, bool *rids_used)
330 : : {
331 : 658546 : bool result = false;
332 : :
333 [ + + ]: 658546 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
334 : : {
335 : 162495 : result = true;
336 : :
337 [ + - + + : 620196 : foreach_ptr(pgpa_advice_target, child_target, target->children)
+ + ]
338 : : {
339 [ + + ]: 295206 : if (!pgpa_identifiers_cover_target(nrids, rids, child_target,
340 : : rids_used))
341 : 132898 : result = false;
342 : : }
343 : : }
344 : : else
345 : : {
346 [ + + ]: 1543215 : for (int i = 0; i < nrids; ++i)
347 : : {
348 [ + + ]: 1047164 : if (pgpa_identifier_matches_target(&rids[i], target))
349 : : {
350 : 304492 : rids_used[i] = true;
351 : 304492 : result = true;
352 : : }
353 : : }
354 : : }
355 : :
356 : 658546 : return result;
357 : : }
|