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 *
3 rhaas@postgresql.org 29 :GNC 133 : pgpa_cstring_advice_tag(pgpa_advice_tag_type advice_tag)
30 : : {
31 [ + + + + : 133 : switch (advice_tag)
+ + + + +
+ + + + +
+ + + + +
- ]
32 : : {
33 : 3 : case PGPA_TAG_BITMAP_HEAP_SCAN:
34 : 3 : return "BITMAP_HEAP_SCAN";
35 : 1 : case PGPA_TAG_FOREIGN_JOIN:
36 : 1 : return "FOREIGN_JOIN";
37 : 8 : case PGPA_TAG_GATHER:
38 : 8 : return "GATHER";
39 : 6 : case PGPA_TAG_GATHER_MERGE:
40 : 6 : return "GATHER_MERGE";
41 : 6 : case PGPA_TAG_HASH_JOIN:
42 : 6 : return "HASH_JOIN";
43 : 6 : case PGPA_TAG_INDEX_ONLY_SCAN:
44 : 6 : return "INDEX_ONLY_SCAN";
45 : 15 : case PGPA_TAG_INDEX_SCAN:
46 : 15 : return "INDEX_SCAN";
47 : 19 : case PGPA_TAG_JOIN_ORDER:
48 : 19 : return "JOIN_ORDER";
49 : 2 : case PGPA_TAG_MERGE_JOIN_MATERIALIZE:
50 : 2 : return "MERGE_JOIN_MATERIALIZE";
51 : 2 : case PGPA_TAG_MERGE_JOIN_PLAIN:
52 : 2 : return "MERGE_JOIN_PLAIN";
53 : 3 : case PGPA_TAG_NESTED_LOOP_MATERIALIZE:
54 : 3 : return "NESTED_LOOP_MATERIALIZE";
55 : 2 : case PGPA_TAG_NESTED_LOOP_MEMOIZE:
56 : 2 : return "NESTED_LOOP_MEMOIZE";
57 : 5 : case PGPA_TAG_NESTED_LOOP_PLAIN:
58 : 5 : return "NESTED_LOOP_PLAIN";
59 : 7 : case PGPA_TAG_NO_GATHER:
60 : 7 : return "NO_GATHER";
61 : 8 : case PGPA_TAG_PARTITIONWISE:
62 : 8 : return "PARTITIONWISE";
63 : 6 : case PGPA_TAG_SEMIJOIN_NON_UNIQUE:
64 : 6 : return "SEMIJOIN_NON_UNIQUE";
65 : 7 : case PGPA_TAG_SEMIJOIN_UNIQUE:
66 : 7 : return "SEMIJOIN_UNIQUE";
67 : 23 : case PGPA_TAG_SEQ_SCAN:
68 : 23 : return "SEQ_SCAN";
69 : 4 : case PGPA_TAG_TID_SCAN:
70 : 4 : return "TID_SCAN";
71 : : }
72 : :
3 rhaas@postgresql.org 73 :UNC 0 : pg_unreachable();
74 : : return NULL;
75 : : }
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 return an arbitrary value.
83 : : */
84 : : pgpa_advice_tag_type
3 rhaas@postgresql.org 85 :GNC 777 : pgpa_parse_advice_tag(const char *tag, bool *fail)
86 : : {
87 : 777 : *fail = false;
88 : :
89 [ + + + + : 777 : switch (tag[0])
+ + + + +
+ + + ]
90 : : {
91 : 10 : case 'b':
92 [ + + ]: 10 : if (strcmp(tag, "bitmap_heap_scan") == 0)
93 : 6 : return PGPA_TAG_BITMAP_HEAP_SCAN;
94 : 4 : break;
95 : 76 : case 'f':
96 [ + + ]: 76 : if (strcmp(tag, "foreign_join") == 0)
97 : 8 : return PGPA_TAG_FOREIGN_JOIN;
98 : 68 : break;
99 : 55 : case 'g':
100 [ + + ]: 55 : if (strcmp(tag, "gather") == 0)
101 : 29 : return PGPA_TAG_GATHER;
102 [ + + ]: 26 : if (strcmp(tag, "gather_merge") == 0)
103 : 20 : return PGPA_TAG_GATHER_MERGE;
104 : 6 : break;
105 : 24 : case 'h':
106 [ + - ]: 24 : if (strcmp(tag, "hash_join") == 0)
107 : 24 : return PGPA_TAG_HASH_JOIN;
3 rhaas@postgresql.org 108 :UNC 0 : break;
3 rhaas@postgresql.org 109 :GNC 38 : 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;
3 rhaas@postgresql.org 114 :UNC 0 : break;
3 rhaas@postgresql.org 115 :GNC 40 : case 'j':
116 [ + - ]: 40 : if (strcmp(tag, "join_order") == 0)
117 : 40 : return PGPA_TAG_JOIN_ORDER;
3 rhaas@postgresql.org 118 :UNC 0 : break;
3 rhaas@postgresql.org 119 :GNC 16 : 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;
3 rhaas@postgresql.org 124 :UNC 0 : break;
3 rhaas@postgresql.org 125 :GNC 58 : 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 : 78 : case 'p':
136 [ + + ]: 78 : if (strcmp(tag, "partitionwise") == 0)
137 : 16 : return PGPA_TAG_PARTITIONWISE;
138 : 62 : break;
139 : 231 : case 's':
140 [ + + ]: 231 : if (strcmp(tag, "semijoin_non_unique") == 0)
141 : 24 : return PGPA_TAG_SEMIJOIN_NON_UNIQUE;
142 [ + + ]: 207 : if (strcmp(tag, "semijoin_unique") == 0)
143 : 28 : return PGPA_TAG_SEMIJOIN_UNIQUE;
144 [ + + ]: 179 : if (strcmp(tag, "seq_scan") == 0)
145 : 51 : return PGPA_TAG_SEQ_SCAN;
146 : 128 : break;
147 : 7 : case 't':
148 [ + - ]: 7 : if (strcmp(tag, "tid_scan") == 0)
149 : 7 : return PGPA_TAG_TID_SCAN;
3 rhaas@postgresql.org 150 :UNC 0 : break;
151 : : }
152 : :
153 : : /* didn't work out */
3 rhaas@postgresql.org 154 :GNC 418 : *fail = true;
155 : :
156 : : /* return an arbitrary value to unwind the call stack */
157 : 418 : return PGPA_TAG_SEQ_SCAN;
158 : : }
159 : :
160 : : /*
161 : : * Format a pgpa_advice_target as a string and append result to a StringInfo.
162 : : */
163 : : void
164 : 202 : pgpa_format_advice_target(StringInfo str, pgpa_advice_target *target)
165 : : {
166 [ + + ]: 202 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
167 : : {
168 : 33 : bool first = true;
169 : : 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 : : }
185 : 33 : appendStringInfoChar(str, delims[1]);
186 : : }
187 : : else
188 : : {
189 : : const char *rt_identifier;
190 : :
191 : 169 : rt_identifier = pgpa_identifier_string(&target->rid);
192 : 169 : appendStringInfoString(str, rt_identifier);
193 : : }
194 : 202 : }
195 : :
196 : : /*
197 : : * Format a pgpa_index_target as a string and append result to a StringInfo.
198 : : */
199 : : void
200 : 21 : pgpa_format_index_target(StringInfo str, pgpa_index_target *itarget)
201 : : {
202 [ + + ]: 21 : if (itarget->indnamespace != NULL)
203 : 4 : appendStringInfo(str, "%s.",
204 : 4 : quote_identifier(itarget->indnamespace));
205 : 21 : appendStringInfoString(str, quote_identifier(itarget->indname));
206 : 21 : }
207 : :
208 : : /*
209 : : * Determine whether two pgpa_index_target objects are exactly identical.
210 : : */
211 : : bool
212 : 2 : pgpa_index_targets_equal(pgpa_index_target *i1, pgpa_index_target *i2)
213 : : {
214 : : /* indnamespace can be NULL, and two NULL values are equal */
215 [ + - + + ]: 2 : if ((i1->indnamespace != NULL || i2->indnamespace != NULL) &&
216 [ - + - - ]: 1 : (i1->indnamespace == NULL || i2->indnamespace == NULL ||
3 rhaas@postgresql.org 217 [ # # ]:UNC 0 : strcmp(i1->indnamespace, i2->indnamespace) != 0))
3 rhaas@postgresql.org 218 :GNC 1 : return false;
219 [ - + ]: 1 : if (strcmp(i1->indname, i2->indname) != 0)
3 rhaas@postgresql.org 220 :UNC 0 : return false;
221 : :
3 rhaas@postgresql.org 222 :GNC 1 : return true;
223 : : }
224 : :
225 : : /*
226 : : * Check whether an identifier matches an any part of an advice target.
227 : : */
228 : : bool
229 : 1629 : pgpa_identifier_matches_target(pgpa_identifier *rid, pgpa_advice_target *target)
230 : : {
231 : : /* For non-identifiers, check all descendants. */
232 [ + + ]: 1629 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
233 : : {
234 [ + - + - : 297 : foreach_ptr(pgpa_advice_target, child_target, target->children)
+ - ]
235 : : {
236 [ + + ]: 297 : if (pgpa_identifier_matches_target(rid, child_target))
237 : 175 : return true;
238 : : }
3 rhaas@postgresql.org 239 :UNC 0 : return false;
240 : : }
241 : :
242 : : /* Straightforward comparisons of alias name and occurrence number. */
3 rhaas@postgresql.org 243 [ + + ]:GNC 1454 : if (strcmp(rid->alias_name, target->rid.alias_name) != 0)
244 : 733 : return false;
245 [ + + ]: 721 : if (rid->occurrence != target->rid.occurrence)
246 : 4 : return false;
247 : :
248 : : /*
249 : : * If a relation identifier mentions a partition name, it should also
250 : : * specify a partition schema. But the target may leave the schema NULL to
251 : : * match anything.
252 : : */
253 [ + + - + ]: 717 : Assert(rid->partnsp != NULL || rid->partrel == NULL);
254 [ + + + + ]: 717 : if (rid->partnsp != NULL && target->rid.partnsp != NULL &&
255 [ - + ]: 20 : strcmp(rid->partnsp, target->rid.partnsp) != 0)
3 rhaas@postgresql.org 256 :UNC 0 : return false;
257 : :
258 : : /*
259 : : * These fields can be NULL on either side, but NULL only matches another
260 : : * NULL.
261 : : */
3 rhaas@postgresql.org 262 [ + + ]:GNC 717 : if (!strings_equal_or_both_null(rid->partrel, target->rid.partrel))
263 : 11 : return false;
264 [ - + ]: 706 : if (!strings_equal_or_both_null(rid->plan_name, target->rid.plan_name))
3 rhaas@postgresql.org 265 :UNC 0 : return false;
266 : :
3 rhaas@postgresql.org 267 :GNC 706 : return true;
268 : : }
269 : :
270 : : /*
271 : : * Match identifiers to advice targets and return an enum value indicating
272 : : * the relationship between the set of keys and the set of targets.
273 : : *
274 : : * See the comments for pgpa_itm_type.
275 : : */
276 : : pgpa_itm_type
277 : 561 : pgpa_identifiers_match_target(int nrids, pgpa_identifier *rids,
278 : : pgpa_advice_target *target)
279 : : {
280 : 561 : bool all_rids_used = true;
281 : 561 : bool any_rids_used = false;
282 : : bool all_targets_used;
283 : 561 : bool *rids_used = palloc0_array(bool, nrids);
284 : :
285 : : all_targets_used =
286 : 561 : pgpa_identifiers_cover_target(nrids, rids, target, rids_used);
287 : :
288 [ + + ]: 1338 : for (int i = 0; i < nrids; ++i)
289 : : {
290 [ + + ]: 777 : if (rids_used[i])
291 : 435 : any_rids_used = true;
292 : : else
293 : 342 : all_rids_used = false;
294 : : }
295 : :
296 [ + + ]: 561 : if (all_rids_used)
297 : : {
298 [ + + ]: 245 : if (all_targets_used)
299 : 197 : return PGPA_ITM_EQUAL;
300 : : else
301 : 48 : return PGPA_ITM_KEYS_ARE_SUBSET;
302 : : }
303 : : else
304 : : {
305 [ + + ]: 316 : if (all_targets_used)
306 : 94 : return PGPA_ITM_TARGETS_ARE_SUBSET;
307 [ + + ]: 222 : else if (any_rids_used)
308 : 38 : return PGPA_ITM_INTERSECTING;
309 : : else
310 : 184 : return PGPA_ITM_DISJOINT;
311 : : }
312 : : }
313 : :
314 : : /*
315 : : * Returns true if every target or sub-target is matched by at least one
316 : : * identifier, and otherwise false.
317 : : *
318 : : * Also sets rids_used[i] = true for each idenifier that matches at least one
319 : : * target.
320 : : */
321 : : static bool
322 : 1035 : pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
323 : : pgpa_advice_target *target, bool *rids_used)
324 : : {
325 : 1035 : bool result = false;
326 : :
327 [ + + ]: 1035 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
328 : : {
329 : 315 : result = true;
330 : :
331 [ + - + + : 1104 : foreach_ptr(pgpa_advice_target, child_target, target->children)
+ + ]
332 : : {
333 [ + + ]: 474 : if (!pgpa_identifiers_cover_target(nrids, rids, child_target,
334 : : rids_used))
335 : 215 : result = false;
336 : : }
337 : : }
338 : : else
339 : : {
340 [ + + ]: 1777 : for (int i = 0; i < nrids; ++i)
341 : : {
342 [ + + ]: 1057 : if (pgpa_identifier_matches_target(&rids[i], target))
343 : : {
344 : 435 : rids_used[i] = true;
345 : 435 : result = true;
346 : : }
347 : : }
348 : : }
349 : :
350 : 1035 : return result;
351 : : }
|