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 "miscadmin.h"
19 : : #include "utils/array.h"
20 : : #include "utils/builtins.h"
21 : :
22 : : static bool pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
23 : : pgpa_advice_target *target,
24 : : bool *rids_used);
25 : :
26 : : /*
27 : : * Get a C string that corresponds to the specified advice tag.
28 : : */
29 : : char *
238 rhaas@postgresql.org 30 :CBC 145525 : pgpa_cstring_advice_tag(pgpa_advice_tag_type advice_tag)
31 : : {
32 [ + + + + : 145525 : switch (advice_tag)
+ + + + +
+ + + + +
+ + + + +
+ - ]
33 : : {
34 : 2283 : case PGPA_TAG_BITMAP_HEAP_SCAN:
35 : 2283 : return "BITMAP_HEAP_SCAN";
224 36 : 434 : case PGPA_TAG_DO_NOT_SCAN:
37 : 434 : return "DO_NOT_SCAN";
238 38 : 1 : case PGPA_TAG_FOREIGN_JOIN:
39 : 1 : return "FOREIGN_JOIN";
40 : 174 : case PGPA_TAG_GATHER:
41 : 174 : return "GATHER";
42 : 58 : case PGPA_TAG_GATHER_MERGE:
43 : 58 : return "GATHER_MERGE";
44 : 4435 : case PGPA_TAG_HASH_JOIN:
45 : 4435 : return "HASH_JOIN";
46 : 1689 : case PGPA_TAG_INDEX_ONLY_SCAN:
47 : 1689 : return "INDEX_ONLY_SCAN";
48 : 13086 : case PGPA_TAG_INDEX_SCAN:
49 : 13086 : return "INDEX_SCAN";
50 : 11071 : case PGPA_TAG_JOIN_ORDER:
51 : 11071 : return "JOIN_ORDER";
52 : 31 : case PGPA_TAG_MERGE_JOIN_MATERIALIZE:
53 : 31 : return "MERGE_JOIN_MATERIALIZE";
54 : 570 : case PGPA_TAG_MERGE_JOIN_PLAIN:
55 : 570 : return "MERGE_JOIN_PLAIN";
56 : 566 : case PGPA_TAG_NESTED_LOOP_MATERIALIZE:
57 : 566 : return "NESTED_LOOP_MATERIALIZE";
58 : 243 : case PGPA_TAG_NESTED_LOOP_MEMOIZE:
59 : 243 : return "NESTED_LOOP_MEMOIZE";
60 : 9271 : case PGPA_TAG_NESTED_LOOP_PLAIN:
61 : 9271 : return "NESTED_LOOP_PLAIN";
62 : 71971 : case PGPA_TAG_NO_GATHER:
63 : 71971 : return "NO_GATHER";
64 : 2251 : case PGPA_TAG_PARTITIONWISE:
65 : 2251 : return "PARTITIONWISE";
66 : 623 : case PGPA_TAG_SEMIJOIN_NON_UNIQUE:
67 : 623 : return "SEMIJOIN_NON_UNIQUE";
68 : 82 : case PGPA_TAG_SEMIJOIN_UNIQUE:
69 : 82 : return "SEMIJOIN_UNIQUE";
70 : 26266 : case PGPA_TAG_SEQ_SCAN:
71 : 26266 : return "SEQ_SCAN";
72 : 420 : case PGPA_TAG_TID_SCAN:
73 : 420 : return "TID_SCAN";
74 : : }
75 : :
238 rhaas@postgresql.org 76 :UBC 0 : pg_unreachable();
77 : : return NULL;
78 : : }
79 : :
80 : : /*
81 : : * Convert an advice tag, formatted as a string that has already been
82 : : * downcased as appropriate, to a pgpa_advice_tag_type.
83 : : *
84 : : * If we succeed, set *fail = false and return the result; if we fail,
85 : : * set *fail = true and return an arbitrary value.
86 : : */
87 : : pgpa_advice_tag_type
238 rhaas@postgresql.org 88 :CBC 345257 : pgpa_parse_advice_tag(const char *tag, bool *fail)
89 : : {
90 : 345257 : *fail = false;
91 : :
92 [ + + + + : 345257 : switch (tag[0])
+ + + + +
+ + + + ]
93 : : {
94 : 8998 : case 'b':
95 [ + + ]: 8998 : if (strcmp(tag, "bitmap_heap_scan") == 0)
96 : 2190 : return PGPA_TAG_BITMAP_HEAP_SCAN;
97 : 6808 : break;
224 98 : 2836 : case 'd':
99 [ + + ]: 2836 : if (strcmp(tag, "do_not_scan") == 0)
100 : 234 : return PGPA_TAG_DO_NOT_SCAN;
101 : 2602 : break;
238 102 : 5307 : case 'f':
103 [ + + ]: 5307 : if (strcmp(tag, "foreign_join") == 0)
104 : 10 : return PGPA_TAG_FOREIGN_JOIN;
105 : 5297 : break;
106 : 2388 : case 'g':
107 [ + + ]: 2388 : if (strcmp(tag, "gather") == 0)
108 : 345 : return PGPA_TAG_GATHER;
109 [ + + ]: 2043 : if (strcmp(tag, "gather_merge") == 0)
110 : 112 : return PGPA_TAG_GATHER_MERGE;
111 : 1931 : break;
112 : 6808 : case 'h':
113 [ + + ]: 6808 : if (strcmp(tag, "hash_join") == 0)
114 : 6115 : return PGPA_TAG_HASH_JOIN;
115 : 693 : break;
116 : 17577 : case 'i':
117 [ + + ]: 17577 : if (strcmp(tag, "index_scan") == 0)
118 : 7822 : return PGPA_TAG_INDEX_SCAN;
119 [ + + ]: 9755 : if (strcmp(tag, "index_only_scan") == 0)
120 : 1458 : return PGPA_TAG_INDEX_ONLY_SCAN;
121 : 8297 : break;
122 : 11844 : case 'j':
123 [ + + ]: 11844 : if (strcmp(tag, "join_order") == 0)
124 : 11105 : return PGPA_TAG_JOIN_ORDER;
125 : 739 : break;
126 : 4597 : case 'm':
127 [ + + ]: 4597 : if (strcmp(tag, "merge_join_materialize") == 0)
128 : 66 : return PGPA_TAG_MERGE_JOIN_MATERIALIZE;
129 [ + + ]: 4531 : if (strcmp(tag, "merge_join_plain") == 0)
130 : 1016 : return PGPA_TAG_MERGE_JOIN_PLAIN;
131 : 3515 : break;
132 : 62175 : case 'n':
133 [ + + ]: 62175 : if (strcmp(tag, "nested_loop_materialize") == 0)
134 : 1032 : return PGPA_TAG_NESTED_LOOP_MATERIALIZE;
135 [ + + ]: 61143 : if (strcmp(tag, "nested_loop_memoize") == 0)
136 : 462 : return PGPA_TAG_NESTED_LOOP_MEMOIZE;
137 [ + + ]: 60681 : if (strcmp(tag, "nested_loop_plain") == 0)
138 : 11420 : return PGPA_TAG_NESTED_LOOP_PLAIN;
139 [ + + ]: 49261 : if (strcmp(tag, "no_gather") == 0)
140 : 43405 : return PGPA_TAG_NO_GATHER;
141 : 5856 : break;
142 : 88997 : case 'p':
143 [ + + ]: 88997 : if (strcmp(tag, "partitionwise") == 0)
144 : 3246 : return PGPA_TAG_PARTITIONWISE;
145 : 85751 : break;
146 : 41873 : case 's':
147 [ + + ]: 41873 : if (strcmp(tag, "semijoin_non_unique") == 0)
148 : 1176 : return PGPA_TAG_SEMIJOIN_NON_UNIQUE;
149 [ + + ]: 40697 : if (strcmp(tag, "semijoin_unique") == 0)
150 : 156 : return PGPA_TAG_SEMIJOIN_UNIQUE;
151 [ + + ]: 40541 : if (strcmp(tag, "seq_scan") == 0)
152 : 16659 : return PGPA_TAG_SEQ_SCAN;
153 : 23882 : break;
154 : 25625 : case 't':
155 [ + + ]: 25625 : if (strcmp(tag, "tid_scan") == 0)
156 : 403 : return PGPA_TAG_TID_SCAN;
157 : 25222 : break;
158 : : }
159 : :
160 : : /* didn't work out */
161 : 236825 : *fail = true;
162 : :
163 : : /* return an arbitrary value to unwind the call stack */
164 : 236825 : return PGPA_TAG_SEQ_SCAN;
165 : : }
166 : :
167 : : /*
168 : : * Format a pgpa_advice_target as a string and append result to a StringInfo.
169 : : */
170 : : void
171 : 174383 : pgpa_format_advice_target(StringInfo str, pgpa_advice_target *target)
172 : : {
57 173 : 174383 : check_stack_depth();
174 : :
238 175 [ + + ]: 174383 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
176 : : {
177 : 12599 : bool first = true;
178 : : char *delims;
179 : :
180 [ + + ]: 12599 : if (target->ttype == PGPA_TARGET_UNORDERED_LIST)
181 : 24 : delims = "{}";
182 : : else
183 : 12575 : delims = "()";
184 : :
185 : 12599 : appendStringInfoChar(str, delims[0]);
186 [ + - + + : 54056 : foreach_ptr(pgpa_advice_target, child_target, target->children)
+ + ]
187 : : {
188 [ + + ]: 28858 : if (first)
189 : 12599 : first = false;
190 : : else
191 : 16259 : appendStringInfoChar(str, ' ');
192 : 28858 : pgpa_format_advice_target(str, child_target);
193 : : }
194 : 12599 : appendStringInfoChar(str, delims[1]);
195 : : }
196 : : else
197 : : {
198 : : const char *rt_identifier;
199 : :
200 : 161784 : rt_identifier = pgpa_identifier_string(&target->rid);
201 : 161784 : appendStringInfoString(str, rt_identifier);
202 : : }
203 : 174383 : }
204 : :
205 : : /*
206 : : * Format a pgpa_index_target as a string and append result to a StringInfo.
207 : : */
208 : : void
209 : 14775 : pgpa_format_index_target(StringInfo str, pgpa_index_target *itarget)
210 : : {
211 [ + + ]: 14775 : if (itarget->indnamespace != NULL)
212 : 14757 : appendStringInfo(str, "%s.",
213 : 14757 : quote_identifier(itarget->indnamespace));
214 : 14775 : appendStringInfoString(str, quote_identifier(itarget->indname));
215 : 14775 : }
216 : :
217 : : /*
218 : : * Determine whether two pgpa_index_target objects are exactly identical.
219 : : */
220 : : bool
221 : 2 : pgpa_index_targets_equal(pgpa_index_target *i1, pgpa_index_target *i2)
222 : : {
223 : : /* indnamespace can be NULL, and two NULL values are equal */
224 [ + - + + ]: 2 : if ((i1->indnamespace != NULL || i2->indnamespace != NULL) &&
225 [ - + - - ]: 1 : (i1->indnamespace == NULL || i2->indnamespace == NULL ||
238 rhaas@postgresql.org 226 [ # # ]:UBC 0 : strcmp(i1->indnamespace, i2->indnamespace) != 0))
238 rhaas@postgresql.org 227 :CBC 1 : return false;
228 [ - + ]: 1 : if (strcmp(i1->indname, i2->indname) != 0)
238 rhaas@postgresql.org 229 :UBC 0 : return false;
230 : :
238 rhaas@postgresql.org 231 :CBC 1 : return true;
232 : : }
233 : :
234 : : /*
235 : : * Check whether an identifier matches an any part of an advice target.
236 : : */
237 : : bool
238 : 1390586 : pgpa_identifier_matches_target(pgpa_identifier *rid, pgpa_advice_target *target)
239 : : {
57 240 : 1390586 : check_stack_depth();
241 : :
242 : : /* For non-identifiers, check all descendants. */
238 243 [ + + ]: 1390586 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
244 : : {
245 [ + - + + : 167874 : foreach_ptr(pgpa_advice_target, child_target, target->children)
+ + ]
246 : : {
247 [ + + ]: 161710 : if (pgpa_identifier_matches_target(rid, child_target))
248 : 78858 : return true;
249 : : }
250 : 3082 : return false;
251 : : }
252 : :
253 : : /* Straightforward comparisons of alias name and occurrence number. */
254 [ + + ]: 1308646 : if (strcmp(rid->alias_name, target->rid.alias_name) != 0)
255 : 711144 : return false;
256 [ + + ]: 597502 : if (rid->occurrence != target->rid.occurrence)
257 : 14038 : return false;
258 : :
259 : : /*
260 : : * A relation identifier should either include both of partition name and
261 : : * partition schema, or neither one.
262 : : */
50 263 [ - + ]: 583464 : Assert((rid->partnsp == NULL) == (rid->partrel == NULL));
264 : :
265 : : /*
266 : : * These fields can be NULL on either side, but NULL only matches another
267 : : * NULL.
268 : : */
269 [ + + ]: 583464 : if (!strings_equal_or_both_null(rid->partnsp, target->rid.partnsp))
270 : 11 : return false;
238 271 [ - + ]: 583453 : if (!strings_equal_or_both_null(rid->partrel, target->rid.partrel))
238 rhaas@postgresql.org 272 :UBC 0 : return false;
238 rhaas@postgresql.org 273 [ - + ]:CBC 583453 : if (!strings_equal_or_both_null(rid->plan_name, target->rid.plan_name))
238 rhaas@postgresql.org 274 :UBC 0 : return false;
275 : :
238 rhaas@postgresql.org 276 :CBC 583453 : return true;
277 : : }
278 : :
279 : : /*
280 : : * Match identifiers to advice targets and return an enum value indicating
281 : : * the relationship between the set of keys and the set of targets.
282 : : *
283 : : * See the comments for pgpa_itm_type.
284 : : */
285 : : pgpa_itm_type
286 : 359682 : pgpa_identifiers_match_target(int nrids, pgpa_identifier *rids,
287 : : pgpa_advice_target *target)
288 : : {
289 : 359682 : bool all_rids_used = true;
290 : 359682 : bool any_rids_used = false;
291 : : bool all_targets_used;
292 : 359682 : bool *rids_used = palloc0_array(bool, nrids);
293 : :
294 : : /*
295 : : * This function is called from within various loops within
296 : : * pgpa_planner.c; to avoid needing a separate CHECK_FOR_INTERRUPTS() in
297 : : * each one, we check here instead.
298 : : */
57 299 [ + + ]: 359682 : CHECK_FOR_INTERRUPTS();
300 : :
301 : : all_targets_used =
238 302 : 359682 : pgpa_identifiers_cover_target(nrids, rids, target, rids_used);
303 : :
304 [ + + ]: 984814 : for (int i = 0; i < nrids; ++i)
305 : : {
306 [ + + ]: 625132 : if (rids_used[i])
307 : 289653 : any_rids_used = true;
308 : : else
309 : 335479 : all_rids_used = false;
310 : : }
311 : :
312 [ + + ]: 359682 : if (all_rids_used)
313 : : {
314 [ + + ]: 142988 : if (all_targets_used)
315 : 127201 : return PGPA_ITM_EQUAL;
316 : : else
317 : 15787 : return PGPA_ITM_KEYS_ARE_SUBSET;
318 : : }
319 : : else
320 : : {
321 [ + + ]: 216694 : if (all_targets_used)
322 : 84372 : return PGPA_ITM_TARGETS_ARE_SUBSET;
323 [ + + ]: 132322 : else if (any_rids_used)
324 : 13608 : return PGPA_ITM_INTERSECTING;
325 : : else
326 : 118714 : return PGPA_ITM_DISJOINT;
327 : : }
328 : : }
329 : :
330 : : /*
331 : : * Returns true if every target or sub-target is matched by at least one
332 : : * identifier, and otherwise false.
333 : : *
334 : : * Also sets rids_used[i] = true for each identifier that matches at least one
335 : : * target.
336 : : */
337 : : static bool
338 : 611391 : pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
339 : : pgpa_advice_target *target, bool *rids_used)
340 : : {
341 : 611391 : bool result = false;
342 : :
57 343 : 611391 : check_stack_depth();
344 : :
238 345 [ + + ]: 611391 : if (target->ttype != PGPA_TARGET_IDENTIFIER)
346 : : {
347 : 147410 : result = true;
348 : :
349 [ + - + + : 546529 : foreach_ptr(pgpa_advice_target, child_target, target->children)
+ + ]
350 : : {
351 [ + + ]: 251709 : if (!pgpa_identifiers_cover_target(nrids, rids, child_target,
352 : : rids_used))
353 : 110494 : result = false;
354 : : }
355 : : }
356 : : else
357 : : {
358 [ + + ]: 1394233 : for (int i = 0; i < nrids; ++i)
359 : : {
360 [ + + ]: 930252 : if (pgpa_identifier_matches_target(&rids[i], target))
361 : : {
362 : 289653 : rids_used[i] = true;
363 : 289653 : result = true;
364 : : }
365 : : }
366 : : }
367 : :
368 : 611391 : return result;
369 : : }
|