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 *
29 144034 : pgpa_cstring_advice_tag(pgpa_advice_tag_type advice_tag)
30 : {
31 144034 : switch (advice_tag)
32 : {
33 2778 : case PGPA_TAG_BITMAP_HEAP_SCAN:
34 2778 : return "BITMAP_HEAP_SCAN";
35 416 : case PGPA_TAG_DO_NOT_SCAN:
36 416 : return "DO_NOT_SCAN";
37 1 : case PGPA_TAG_FOREIGN_JOIN:
38 1 : return "FOREIGN_JOIN";
39 172 : case PGPA_TAG_GATHER:
40 172 : return "GATHER";
41 61 : case PGPA_TAG_GATHER_MERGE:
42 61 : return "GATHER_MERGE";
43 4386 : case PGPA_TAG_HASH_JOIN:
44 4386 : return "HASH_JOIN";
45 1882 : case PGPA_TAG_INDEX_ONLY_SCAN:
46 1882 : return "INDEX_ONLY_SCAN";
47 11857 : case PGPA_TAG_INDEX_SCAN:
48 11857 : return "INDEX_SCAN";
49 11137 : case PGPA_TAG_JOIN_ORDER:
50 11137 : return "JOIN_ORDER";
51 27 : case PGPA_TAG_MERGE_JOIN_MATERIALIZE:
52 27 : return "MERGE_JOIN_MATERIALIZE";
53 541 : case PGPA_TAG_MERGE_JOIN_PLAIN:
54 541 : return "MERGE_JOIN_PLAIN";
55 500 : case PGPA_TAG_NESTED_LOOP_MATERIALIZE:
56 500 : return "NESTED_LOOP_MATERIALIZE";
57 208 : case PGPA_TAG_NESTED_LOOP_MEMOIZE:
58 208 : return "NESTED_LOOP_MEMOIZE";
59 9139 : case PGPA_TAG_NESTED_LOOP_PLAIN:
60 9139 : return "NESTED_LOOP_PLAIN";
61 70920 : case PGPA_TAG_NO_GATHER:
62 70920 : return "NO_GATHER";
63 2174 : case PGPA_TAG_PARTITIONWISE:
64 2174 : return "PARTITIONWISE";
65 624 : case PGPA_TAG_SEMIJOIN_NON_UNIQUE:
66 624 : return "SEMIJOIN_NON_UNIQUE";
67 79 : case PGPA_TAG_SEMIJOIN_UNIQUE:
68 79 : return "SEMIJOIN_UNIQUE";
69 26712 : case PGPA_TAG_SEQ_SCAN:
70 26712 : return "SEQ_SCAN";
71 420 : case PGPA_TAG_TID_SCAN:
72 420 : return "TID_SCAN";
73 : }
74 :
75 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
87 340318 : pgpa_parse_advice_tag(const char *tag, bool *fail)
88 : {
89 340318 : *fail = false;
90 :
91 340318 : switch (tag[0])
92 : {
93 9284 : case 'b':
94 9284 : if (strcmp(tag, "bitmap_heap_scan") == 0)
95 2615 : return PGPA_TAG_BITMAP_HEAP_SCAN;
96 6669 : break;
97 3266 : case 'd':
98 3266 : if (strcmp(tag, "do_not_scan") == 0)
99 430 : return PGPA_TAG_DO_NOT_SCAN;
100 2836 : break;
101 5306 : case 'f':
102 5306 : if (strcmp(tag, "foreign_join") == 0)
103 8 : return PGPA_TAG_FOREIGN_JOIN;
104 5298 : break;
105 2467 : case 'g':
106 2467 : if (strcmp(tag, "gather") == 0)
107 339 : return PGPA_TAG_GATHER;
108 2128 : if (strcmp(tag, "gather_merge") == 0)
109 118 : return PGPA_TAG_GATHER_MERGE;
110 2010 : break;
111 5699 : case 'h':
112 5699 : if (strcmp(tag, "hash_join") == 0)
113 5053 : return PGPA_TAG_HASH_JOIN;
114 646 : break;
115 16716 : case 'i':
116 16716 : if (strcmp(tag, "index_scan") == 0)
117 6980 : return PGPA_TAG_INDEX_SCAN;
118 9736 : if (strcmp(tag, "index_only_scan") == 0)
119 1672 : return PGPA_TAG_INDEX_ONLY_SCAN;
120 8064 : break;
121 11832 : case 'j':
122 11832 : if (strcmp(tag, "join_order") == 0)
123 11160 : return PGPA_TAG_JOIN_ORDER;
124 672 : break;
125 4252 : case 'm':
126 4252 : if (strcmp(tag, "merge_join_materialize") == 0)
127 58 : return PGPA_TAG_MERGE_JOIN_MATERIALIZE;
128 4194 : if (strcmp(tag, "merge_join_plain") == 0)
129 904 : return PGPA_TAG_MERGE_JOIN_PLAIN;
130 3290 : break;
131 63603 : case 'n':
132 63603 : if (strcmp(tag, "nested_loop_materialize") == 0)
133 910 : return PGPA_TAG_NESTED_LOOP_MATERIALIZE;
134 62693 : if (strcmp(tag, "nested_loop_memoize") == 0)
135 394 : return PGPA_TAG_NESTED_LOOP_MEMOIZE;
136 62299 : if (strcmp(tag, "nested_loop_plain") == 0)
137 12714 : return PGPA_TAG_NESTED_LOOP_PLAIN;
138 49585 : if (strcmp(tag, "no_gather") == 0)
139 43162 : return PGPA_TAG_NO_GATHER;
140 6423 : break;
141 83629 : case 'p':
142 83629 : if (strcmp(tag, "partitionwise") == 0)
143 3326 : return PGPA_TAG_PARTITIONWISE;
144 80303 : break;
145 39631 : case 's':
146 39631 : if (strcmp(tag, "semijoin_non_unique") == 0)
147 1178 : return PGPA_TAG_SEMIJOIN_NON_UNIQUE;
148 38453 : if (strcmp(tag, "semijoin_unique") == 0)
149 150 : return PGPA_TAG_SEMIJOIN_UNIQUE;
150 38303 : if (strcmp(tag, "seq_scan") == 0)
151 16367 : return PGPA_TAG_SEQ_SCAN;
152 21936 : break;
153 23637 : case 't':
154 23637 : if (strcmp(tag, "tid_scan") == 0)
155 403 : return PGPA_TAG_TID_SCAN;
156 23234 : break;
157 : }
158 :
159 : /* didn't work out */
160 232377 : *fail = true;
161 :
162 : /* return an arbitrary value to unwind the call stack */
163 232377 : 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 172434 : pgpa_format_advice_target(StringInfo str, pgpa_advice_target *target)
171 : {
172 172434 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
173 : {
174 12516 : bool first = true;
175 : char *delims;
176 :
177 12516 : if (target->ttype == PGPA_TARGET_UNORDERED_LIST)
178 14 : delims = "{}";
179 : else
180 12502 : delims = "()";
181 :
182 12516 : appendStringInfoChar(str, delims[0]);
183 53432 : foreach_ptr(pgpa_advice_target, child_target, target->children)
184 : {
185 28400 : if (first)
186 12516 : first = false;
187 : else
188 15884 : appendStringInfoChar(str, ' ');
189 28400 : pgpa_format_advice_target(str, child_target);
190 : }
191 12516 : appendStringInfoChar(str, delims[1]);
192 : }
193 : else
194 : {
195 : const char *rt_identifier;
196 :
197 159918 : rt_identifier = pgpa_identifier_string(&target->rid);
198 159918 : appendStringInfoString(str, rt_identifier);
199 : }
200 172434 : }
201 :
202 : /*
203 : * Format a pgpa_index_target as a string and append result to a StringInfo.
204 : */
205 : void
206 13739 : pgpa_format_index_target(StringInfo str, pgpa_index_target *itarget)
207 : {
208 13739 : if (itarget->indnamespace != NULL)
209 13721 : appendStringInfo(str, "%s.",
210 13721 : quote_identifier(itarget->indnamespace));
211 13739 : appendStringInfoString(str, quote_identifier(itarget->indname));
212 13739 : }
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 ||
223 0 : strcmp(i1->indnamespace, i2->indnamespace) != 0))
224 1 : return false;
225 1 : if (strcmp(i1->indname, i2->indname) != 0)
226 0 : return false;
227 :
228 1 : return true;
229 : }
230 :
231 : /*
232 : * Check whether an identifier matches an any part of an advice target.
233 : */
234 : bool
235 1499967 : pgpa_identifier_matches_target(pgpa_identifier *rid, pgpa_advice_target *target)
236 : {
237 : /* For non-identifiers, check all descendants. */
238 1499967 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
239 : {
240 161900 : foreach_ptr(pgpa_advice_target, child_target, target->children)
241 : {
242 154682 : if (pgpa_identifier_matches_target(rid, child_target))
243 74915 : return true;
244 : }
245 3609 : return false;
246 : }
247 :
248 : /* Straightforward comparisons of alias name and occurrence number. */
249 1421443 : if (strcmp(rid->alias_name, target->rid.alias_name) != 0)
250 815651 : return false;
251 605792 : if (rid->occurrence != target->rid.occurrence)
252 27092 : 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 : Assert(rid->partnsp != NULL || rid->partrel == NULL);
260 578700 : if (rid->partnsp != NULL && target->rid.partnsp != NULL &&
261 48224 : strcmp(rid->partnsp, target->rid.partnsp) != 0)
262 0 : return false;
263 :
264 : /*
265 : * These fields can be NULL on either side, but NULL only matches another
266 : * NULL.
267 : */
268 578700 : if (!strings_equal_or_both_null(rid->partrel, target->rid.partrel))
269 11 : return false;
270 578689 : if (!strings_equal_or_both_null(rid->plan_name, target->rid.plan_name))
271 0 : return false;
272 :
273 578689 : 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 363016 : pgpa_identifiers_match_target(int nrids, pgpa_identifier *rids,
284 : pgpa_advice_target *target)
285 : {
286 363016 : bool all_rids_used = true;
287 363016 : bool any_rids_used = false;
288 : bool all_targets_used;
289 363016 : bool *rids_used = palloc0_array(bool, nrids);
290 :
291 : all_targets_used =
292 363016 : pgpa_identifiers_cover_target(nrids, rids, target, rids_used);
293 :
294 1022413 : for (int i = 0; i < nrids; ++i)
295 : {
296 659397 : if (rids_used[i])
297 306499 : any_rids_used = true;
298 : else
299 352898 : all_rids_used = false;
300 : }
301 :
302 363016 : if (all_rids_used)
303 : {
304 139727 : if (all_targets_used)
305 120889 : return PGPA_ITM_EQUAL;
306 : else
307 18838 : return PGPA_ITM_KEYS_ARE_SUBSET;
308 : }
309 : else
310 : {
311 223289 : if (all_targets_used)
312 85649 : return PGPA_ITM_TARGETS_ARE_SUBSET;
313 137640 : else if (any_rids_used)
314 20866 : return PGPA_ITM_INTERSECTING;
315 : else
316 116774 : 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 idenifier that matches at least one
325 : * target.
326 : */
327 : static bool
328 662250 : pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
329 : pgpa_advice_target *target, bool *rids_used)
330 : {
331 662250 : bool result = false;
332 :
333 662250 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
334 : {
335 162414 : result = true;
336 :
337 624062 : foreach_ptr(pgpa_advice_target, child_target, target->children)
338 : {
339 299234 : if (!pgpa_identifiers_cover_target(nrids, rids, child_target,
340 : rids_used))
341 134960 : result = false;
342 : }
343 : }
344 : else
345 : {
346 1563304 : for (int i = 0; i < nrids; ++i)
347 : {
348 1063468 : if (pgpa_identifier_matches_target(&rids[i], target))
349 : {
350 306499 : rids_used[i] = true;
351 306499 : result = true;
352 : }
353 : }
354 : }
355 :
356 662250 : return result;
357 : }
|