Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pgpa_ast.c
4 : : * additional supporting code related to plan advice parsing
5 : : *
6 : : * Copyright (c) 2016-2024, 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 : 10387 : pgpa_cstring_advice_tag(pgpa_advice_tag_type advice_tag)
30 : : {
31 [ + + + + : 10387 : switch (advice_tag)
+ + + + +
+ + + + +
+ + + + -
+ ]
32 : : {
33 : : case PGPA_TAG_BITMAP_HEAP_SCAN:
34 : 3 : return "BITMAP_HEAP_SCAN";
35 : : case PGPA_TAG_FOREIGN_JOIN:
36 : 1 : return "FOREIGN_JOIN";
37 : : case PGPA_TAG_GATHER:
38 : 3426 : return "GATHER";
39 : : case PGPA_TAG_GATHER_MERGE:
40 : 6 : return "GATHER_MERGE";
41 : : case PGPA_TAG_HASH_JOIN:
42 : 3424 : return "HASH_JOIN";
43 : : case PGPA_TAG_INDEX_ONLY_SCAN:
44 : 6 : return "INDEX_ONLY_SCAN";
45 : : case PGPA_TAG_INDEX_SCAN:
46 : 15 : return "INDEX_SCAN";
47 : : case PGPA_TAG_JOIN_ORDER:
48 : 19 : return "JOIN_ORDER";
49 : : case PGPA_TAG_MERGE_JOIN_MATERIALIZE:
50 : 2 : return "MERGE_JOIN_MATERIALIZE";
51 : : case PGPA_TAG_MERGE_JOIN_PLAIN:
52 : 2 : return "MERGE_JOIN_PLAIN";
53 : : case PGPA_TAG_NESTED_LOOP_MATERIALIZE:
54 : 3 : return "NESTED_LOOP_MATERIALIZE";
55 : : case PGPA_TAG_NESTED_LOOP_MEMOIZE:
56 : 2 : return "NESTED_LOOP_MEMOIZE";
57 : : case PGPA_TAG_NESTED_LOOP_PLAIN:
58 : 5 : return "NESTED_LOOP_PLAIN";
59 : : case PGPA_TAG_NO_GATHER:
60 : 7 : return "NO_GATHER";
61 : : case PGPA_TAG_PARTITIONWISE:
62 : 8 : return "PARTITIONWISE";
63 : : case PGPA_TAG_SEMIJOIN_NON_UNIQUE:
64 : 6 : return "SEMIJOIN_NON_UNIQUE";
65 : : case PGPA_TAG_SEMIJOIN_UNIQUE:
66 : 7 : return "SEMIJOIN_UNIQUE";
67 : : case PGPA_TAG_SEQ_SCAN:
68 : 3441 : return "SEQ_SCAN";
69 : : case PGPA_TAG_TID_SCAN:
70 : 4 : return "TID_SCAN";
71 : : }
72 : :
73 : 0 : pg_unreachable();
74 : : return NULL;
75 : 10387 : }
76 : :
77 : : /*
78 : : * Convert an advice tag, formatted as a string that has already been
79 : : * downcased as appropriate, to a pgpa_advice_tag_type.
80 : : *
81 : : * If we succeed, set *fail = false and return the result; if we fail,
82 : : * set *fail = true and reurn an arbitrary value.
83 : : */
84 : : pgpa_advice_tag_type
85 : 348015 : pgpa_parse_advice_tag(const char *tag, bool *fail)
86 : : {
87 : 348015 : *fail = false;
88 : :
89 [ + + + + : 348015 : switch (tag[0])
+ + + + +
+ + + ]
90 : : {
91 : : case 'b':
92 [ + + ]: 10 : if (strcmp(tag, "bitmap_heap_scan") == 0)
93 : 6 : return PGPA_TAG_BITMAP_HEAP_SCAN;
94 : 4 : break;
95 : : case 'f':
96 [ + + ]: 76 : if (strcmp(tag, "foreign_join") == 0)
97 : 8 : return PGPA_TAG_FOREIGN_JOIN;
98 : 68 : break;
99 : : case 'g':
100 [ + + ]: 86863 : if (strcmp(tag, "gather") == 0)
101 : 86837 : return PGPA_TAG_GATHER;
102 [ + + ]: 26 : if (strcmp(tag, "gather_merge") == 0)
103 : 20 : return PGPA_TAG_GATHER_MERGE;
104 : 6 : break;
105 : : case 'h':
106 [ - + ]: 86832 : if (strcmp(tag, "hash_join") == 0)
107 : 86832 : return PGPA_TAG_HASH_JOIN;
108 : 0 : break;
109 : : case 'i':
110 [ + + ]: 38 : if (strcmp(tag, "index_scan") == 0)
111 : 26 : return PGPA_TAG_INDEX_SCAN;
112 [ - + ]: 12 : if (strcmp(tag, "index_only_scan") == 0)
113 : 12 : return PGPA_TAG_INDEX_ONLY_SCAN;
114 : 0 : break;
115 : : case 'j':
116 [ - + ]: 40 : if (strcmp(tag, "join_order") == 0)
117 : 40 : return PGPA_TAG_JOIN_ORDER;
118 : 0 : break;
119 : : case 'm':
120 [ + + ]: 16 : if (strcmp(tag, "merge_join_materialize") == 0)
121 : 8 : return PGPA_TAG_MERGE_JOIN_MATERIALIZE;
122 [ - + ]: 8 : if (strcmp(tag, "merge_join_plain") == 0)
123 : 8 : return PGPA_TAG_MERGE_JOIN_PLAIN;
124 : 0 : break;
125 : : case 'n':
126 [ + + ]: 58 : if (strcmp(tag, "nested_loop_materialize") == 0)
127 : 12 : return PGPA_TAG_NESTED_LOOP_MATERIALIZE;
128 [ + + ]: 46 : if (strcmp(tag, "nested_loop_memoize") == 0)
129 : 8 : return PGPA_TAG_NESTED_LOOP_MEMOIZE;
130 [ + + ]: 38 : if (strcmp(tag, "nested_loop_plain") == 0)
131 : 20 : return PGPA_TAG_NESTED_LOOP_PLAIN;
132 [ + + ]: 18 : if (strcmp(tag, "no_gather") == 0)
133 : 12 : return PGPA_TAG_NO_GATHER;
134 : 6 : break;
135 : : case 'p':
136 [ + + ]: 78 : if (strcmp(tag, "partitionwise") == 0)
137 : 16 : return PGPA_TAG_PARTITIONWISE;
138 : 62 : break;
139 : : case 's':
140 [ + + ]: 43641 : if (strcmp(tag, "semijoin_non_unique") == 0)
141 : 24 : return PGPA_TAG_SEMIJOIN_NON_UNIQUE;
142 [ + + ]: 43617 : if (strcmp(tag, "semijoin_unique") == 0)
143 : 28 : return PGPA_TAG_SEMIJOIN_UNIQUE;
144 [ + + ]: 43589 : if (strcmp(tag, "seq_scan") == 0)
145 : 43455 : return PGPA_TAG_SEQ_SCAN;
146 : 134 : break;
147 : : case 't':
148 [ + + ]: 43411 : if (strcmp(tag, "tid_scan") == 0)
149 : 7 : return PGPA_TAG_TID_SCAN;
150 : 43404 : break;
151 : : }
152 : :
153 : : /* didn't work out */
154 : 130636 : *fail = true;
155 : :
156 : : /* return an arbitrary value to unwind the call stack */
157 : 130636 : return PGPA_TAG_SEQ_SCAN;
158 : 348015 : }
159 : :
160 : : /*
161 : : * Format a pgpa_advice_target as a string and append result to a StringInfo.
162 : : */
163 : : void
164 : 10456 : pgpa_format_advice_target(StringInfo str, pgpa_advice_target *target)
165 : : {
166 [ + + ]: 10456 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
167 : : {
168 : 33 : bool first = true;
169 : 33 : char *delims;
170 : :
171 [ + + ]: 33 : if (target->ttype == PGPA_TARGET_UNORDERED_LIST)
172 : 1 : delims = "{}";
173 : : else
174 : 32 : delims = "()";
175 : :
176 : 33 : appendStringInfoChar(str, delims[0]);
177 [ + + + - : 135 : foreach_ptr(pgpa_advice_target, child_target, target->children)
+ + + + ]
178 : : {
179 [ + + ]: 69 : if (first)
180 : 33 : first = false;
181 : : else
182 : 36 : appendStringInfoChar(str, ' ');
183 : 69 : pgpa_format_advice_target(str, child_target);
184 : 102 : }
185 : 33 : appendStringInfoChar(str, delims[1]);
186 : 33 : }
187 : : else
188 : : {
189 : 10423 : const char *rt_identifier;
190 : :
191 : 10423 : rt_identifier = pgpa_identifier_string(&target->rid);
192 : 10423 : appendStringInfoString(str, rt_identifier);
193 : 10423 : }
194 : 10456 : }
195 : :
196 : : /*
197 : : * Format a pgpa_index_target as a string and append result to a StringInfo.
198 : : */
199 : : void
200 : 24 : pgpa_format_index_target(StringInfo str, pgpa_index_target *itarget)
201 : : {
202 [ - + ]: 24 : if (itarget->itype != PGPA_INDEX_NAME)
203 : : {
204 : 0 : bool first = true;
205 : :
206 [ # # ]: 0 : if (itarget->itype == PGPA_INDEX_AND)
207 : 0 : appendStringInfoString(str, "&&(");
208 : : else
209 : 0 : appendStringInfoString(str, "||(");
210 : :
211 [ # # # # : 0 : foreach_ptr(pgpa_index_target, child_target, itarget->children)
# # # # ]
212 : : {
213 [ # # ]: 0 : if (first)
214 : 0 : first = false;
215 : : else
216 : 0 : appendStringInfoChar(str, ' ');
217 : 0 : pgpa_format_index_target(str, child_target);
218 : 0 : }
219 : 0 : appendStringInfoChar(str, ')');
220 : 0 : }
221 : : else
222 : : {
223 [ + + ]: 24 : if (itarget->indnamespace != NULL)
224 : 8 : appendStringInfo(str, "%s.",
225 : 4 : quote_identifier(itarget->indnamespace));
226 : 24 : appendStringInfoString(str, quote_identifier(itarget->indname));
227 : : }
228 : 24 : }
229 : :
230 : : /*
231 : : * Determine whether two pgpa_index_target objects are exactly identical.
232 : : */
233 : : bool
234 : 2 : pgpa_index_targets_equal(pgpa_index_target *i1, pgpa_index_target *i2)
235 : : {
236 [ - + ]: 2 : if (i1->itype != i2->itype)
237 : 0 : return false;
238 : :
239 [ - + ]: 2 : if (i1->itype == PGPA_INDEX_NAME)
240 : : {
241 : : /* indnamespace can be NULL, and two NULL values are equal */
242 [ + - + + ]: 2 : if ((i1->indnamespace != NULL || i2->indnamespace != NULL) &&
243 [ - + # # ]: 1 : (i1->indnamespace == NULL || i2->indnamespace == NULL ||
244 : 0 : strcmp(i1->indnamespace, i2->indnamespace) != 0))
245 : 1 : return false;
246 [ - + ]: 1 : if (strcmp(i1->indname, i2->indname) != 0)
247 : 0 : return false;
248 : 1 : }
249 : : else
250 : : {
251 : 0 : int i1_length = list_length(i1->children);
252 : :
253 [ # # ]: 0 : if (i1_length != list_length(i2->children))
254 : 0 : return false;
255 [ # # # # ]: 0 : for (int n = 0; n < i1_length; ++n)
256 : : {
257 : 0 : pgpa_index_target *c1 = list_nth(i1->children, n);
258 : 0 : pgpa_index_target *c2 = list_nth(i2->children, n);
259 : :
260 [ # # ]: 0 : if (!pgpa_index_targets_equal(c1, c2))
261 : 0 : return false;
262 [ # # ]: 0 : }
263 [ # # # ]: 0 : }
264 : :
265 : 1 : return true;
266 : 2 : }
267 : :
268 : : /*
269 : : * Check whether an identifier matches an any part of an advice target.
270 : : */
271 : : bool
272 : 1629 : pgpa_identifier_matches_target(pgpa_identifier *rid, pgpa_advice_target *target)
273 : : {
274 : : /* For non-identifiers, check all descendents. */
275 [ + + ]: 1629 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
276 : : {
277 [ + - + - : 472 : foreach_ptr(pgpa_advice_target, child_target, target->children)
- + + - +
- - + - ]
278 : : {
279 [ + + ]: 297 : if (pgpa_identifier_matches_target(rid, child_target))
280 : 175 : return true;
281 : 122 : }
282 : 0 : return false;
283 : : }
284 : :
285 : : /* Straightforward comparisons of alias name and occcurrence number. */
286 [ + + ]: 1454 : if (strcmp(rid->alias_name, target->rid.alias_name) != 0)
287 : 733 : return false;
288 [ + + ]: 721 : if (rid->occurrence != target->rid.occurrence)
289 : 4 : return false;
290 : :
291 : : /*
292 : : * If a relation identifer mentions a partition name, it should also
293 : : * specify a partition schema. But the target may leave the schema NULL to
294 : : * match anything.
295 : : */
296 [ + + + - ]: 717 : Assert(rid->partnsp != NULL || rid->partrel == NULL);
297 [ + + + + : 717 : if (rid->partnsp != NULL && target->rid.partnsp != NULL &&
+ - ]
298 : 20 : strcmp(rid->partnsp, target->rid.partnsp) != 0)
299 : 0 : return false;
300 : :
301 : : /*
302 : : * These fields can be NULL on either side, but NULL only matches another
303 : : * NULL.
304 : : */
305 [ + + ]: 717 : if (!strings_equal_or_both_null(rid->partrel, target->rid.partrel))
306 : 11 : return false;
307 [ + - ]: 706 : if (!strings_equal_or_both_null(rid->plan_name, target->rid.plan_name))
308 : 0 : return false;
309 : :
310 : 706 : return true;
311 : 1629 : }
312 : :
313 : : /*
314 : : * Match identifiers to advice targets and return an enum value indicating
315 : : * the relationship between the set of keys and the set of targets.
316 : : *
317 : : * See the comments for pgpa_itm_type.
318 : : */
319 : : pgpa_itm_type
320 : 561 : pgpa_identifiers_match_target(int nrids, pgpa_identifier *rids,
321 : : pgpa_advice_target *target)
322 : : {
323 : 561 : bool all_rids_used = true;
324 : 561 : bool any_rids_used = false;
325 : 561 : bool all_targets_used;
326 : 561 : bool *rids_used = palloc0_array(bool, nrids);
327 : :
328 : 561 : all_targets_used =
329 : 561 : pgpa_identifiers_cover_target(nrids, rids, target, rids_used);
330 : :
331 [ + + ]: 1338 : for (int i = 0; i < nrids; ++i)
332 : : {
333 [ + + ]: 777 : if (rids_used[i])
334 : 435 : any_rids_used = true;
335 : : else
336 : 342 : all_rids_used = false;
337 : 777 : }
338 : :
339 [ + + ]: 561 : if (all_rids_used)
340 : : {
341 [ + + ]: 245 : if (all_targets_used)
342 : 197 : return PGPA_ITM_EQUAL;
343 : : else
344 : 48 : return PGPA_ITM_KEYS_ARE_SUBSET;
345 : : }
346 : : else
347 : : {
348 [ + + ]: 316 : if (all_targets_used)
349 : 94 : return PGPA_ITM_TARGETS_ARE_SUBSET;
350 [ + + ]: 222 : else if (any_rids_used)
351 : 38 : return PGPA_ITM_INTERSECTING;
352 : : else
353 : 184 : return PGPA_ITM_DISJOINT;
354 : : }
355 : 561 : }
356 : :
357 : : /*
358 : : * Returns true if every target or sub-target is matched by at least one
359 : : * identifier, and otherwise false.
360 : : *
361 : : * Also sets rids_used[i] = true for each idenifier that matches at least one
362 : : * target.
363 : : */
364 : : static bool
365 : 1035 : pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
366 : : pgpa_advice_target *target, bool *rids_used)
367 : : {
368 : 1035 : bool result = false;
369 : :
370 [ + + ]: 1035 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
371 : : {
372 : 315 : result = true;
373 : :
374 [ + + + - : 1104 : foreach_ptr(pgpa_advice_target, child_target, target->children)
+ + + + ]
375 : : {
376 [ + + + + ]: 948 : if (!pgpa_identifiers_cover_target(nrids, rids, child_target,
377 : 474 : rids_used))
378 : 215 : result = false;
379 : 789 : }
380 : 315 : }
381 : : else
382 : : {
383 [ + + ]: 1777 : for (int i = 0; i < nrids; ++i)
384 : : {
385 [ + + ]: 1057 : if (pgpa_identifier_matches_target(&rids[i], target))
386 : : {
387 : 435 : rids_used[i] = true;
388 : 435 : result = true;
389 : 435 : }
390 : 1057 : }
391 : : }
392 : :
393 : 2070 : return result;
394 : 1035 : }
|