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 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 :
73 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
85 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;
108 0 : break;
109 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;
114 0 : break;
115 40 : case 'j':
116 40 : if (strcmp(tag, "join_order") == 0)
117 40 : return PGPA_TAG_JOIN_ORDER;
118 0 : break;
119 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;
124 0 : break;
125 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;
150 0 : break;
151 : }
152 :
153 : /* didn't work out */
154 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 ||
217 0 : strcmp(i1->indnamespace, i2->indnamespace) != 0))
218 1 : return false;
219 1 : if (strcmp(i1->indname, i2->indname) != 0)
220 0 : return false;
221 :
222 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 : }
239 0 : return false;
240 : }
241 :
242 : /* Straightforward comparisons of alias name and occurrence number. */
243 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 : 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)
256 0 : return false;
257 :
258 : /*
259 : * These fields can be NULL on either side, but NULL only matches another
260 : * NULL.
261 : */
262 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))
265 0 : return false;
266 :
267 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 : }
|