Branch data Line data Source code
1 : : %{
2 : : /*
3 : : * Parser for plan advice
4 : : *
5 : : * Copyright (c) 2000-2025, PostgreSQL Global Development Group
6 : : *
7 : : * contrib/pg_plan_advice/pgpa_parser.y
8 : : */
9 : :
10 : : #include "postgres.h"
11 : :
12 : : #include <float.h>
13 : : #include <math.h>
14 : :
15 : : #include "fmgr.h"
16 : : #include "nodes/miscnodes.h"
17 : : #include "utils/builtins.h"
18 : : #include "utils/float.h"
19 : :
20 : : #include "pgpa_ast.h"
21 : : #include "pgpa_parser.h"
22 : :
23 : : /*
24 : : * Bison doesn't allocate anything that needs to live across parser calls,
25 : : * so we can easily have it use palloc instead of malloc. This prevents
26 : : * memory leaks if we error out during parsing.
27 : : */
28 : : #define YYMALLOC palloc
29 : : #define YYFREE pfree
30 : : %}
31 : :
32 : : /* BISON Declarations */
33 : : %parse-param {List **result}
34 : : %parse-param {char **parse_error_msg_p}
35 : : %parse-param {yyscan_t yyscanner}
36 : : %lex-param {List **result}
37 : : %lex-param {char **parse_error_msg_p}
38 : : %lex-param {yyscan_t yyscanner}
39 : : %pure-parser
40 : : %expect 0
41 : : %name-prefix="pgpa_yy"
42 : :
43 : : %union
44 : : {
45 : : char *str;
46 : : int integer;
47 : : List *list;
48 : : pgpa_advice_item *item;
49 : : pgpa_advice_target *target;
50 : : pgpa_index_target *itarget;
51 : : }
52 : : %token <str> TOK_IDENT TOK_TAG_JOIN_ORDER TOK_TAG_BITMAP TOK_TAG_INDEX
53 : : %token <str> TOK_TAG_SIMPLE TOK_TAG_GENERIC
54 : : %token <integer> TOK_INTEGER
55 : : %token TOK_OR TOK_AND
56 : :
57 : : %type <integer> opt_ri_occurrence
58 : : %type <item> advice_item
59 : : %type <list> advice_item_list bitmap_sublist bitmap_target_list generic_target_list
60 : : %type <list> index_target_list join_order_target_list
61 : : %type <list> opt_partition simple_target_list
62 : : %type <str> identifier opt_plan_name
63 : : %type <target> generic_sublist join_order_sublist
64 : : %type <target> relation_identifier
65 : : %type <itarget> bitmap_target_item index_name
66 : :
67 : : %start parse_toplevel
68 : :
69 : : /* Grammar follows */
70 : : %%
71 : :
72 : : parse_toplevel: advice_item_list
73 : : {
74 : : (void) yynerrs; /* suppress compiler warning */
75 : : *result = $1;
76 : : }
77 : : ;
78 : :
79 : : advice_item_list: advice_item_list advice_item
80 : : { $$ = lappend($1, $2); }
81 : : |
82 : : { $$ = NIL; }
83 : : ;
84 : :
85 : : advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
86 : : {
87 : : $$ = palloc0_object(pgpa_advice_item);
88 : : $$->tag = PGPA_TAG_JOIN_ORDER;
89 : : $$->targets = $3;
90 : : if ($3 == NIL)
91 : : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
92 : : "JOIN_ORDER must have at least one target");
93 : : }
94 : : | TOK_TAG_INDEX '(' index_target_list ')'
95 : : {
96 : : $$ = palloc0_object(pgpa_advice_item);
97 : : if (strcmp($1, "index_only_scan") == 0)
98 : : $$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
99 : : else if (strcmp($1, "index_scan") == 0)
100 : : $$->tag = PGPA_TAG_INDEX_SCAN;
101 : : else
102 : : elog(ERROR, "tag parsing failed: %s", $1);
103 : : $$->targets = $3;
104 : : }
105 : : | TOK_TAG_BITMAP '(' bitmap_target_list ')'
106 : : {
107 : : $$ = palloc0_object(pgpa_advice_item);
108 : : $$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
109 : : $$->targets = $3;
110 : : }
111 : : | TOK_TAG_SIMPLE '(' simple_target_list ')'
112 : : {
113 : : $$ = palloc0_object(pgpa_advice_item);
114 : : if (strcmp($1, "no_gather") == 0)
115 : : $$->tag = PGPA_TAG_NO_GATHER;
116 : : else if (strcmp($1, "seq_scan") == 0)
117 : : $$->tag = PGPA_TAG_SEQ_SCAN;
118 : : else if (strcmp($1, "tid_scan") == 0)
119 : : $$->tag = PGPA_TAG_TID_SCAN;
120 : : else
121 : : elog(ERROR, "tag parsing failed: %s", $1);
122 : : $$->targets = $3;
123 : : }
124 : : | TOK_TAG_GENERIC '(' generic_target_list ')'
125 : : {
126 : : bool fail;
127 : :
128 : : $$ = palloc0_object(pgpa_advice_item);
129 : : $$->tag = pgpa_parse_advice_tag($1, &fail);
130 : : if (fail)
131 : : {
132 : : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
133 : : "unrecognized advice tag");
134 : : }
135 : :
136 : : if ($$->tag == PGPA_TAG_FOREIGN_JOIN)
137 : : {
138 : : foreach_ptr(pgpa_advice_target, target, $3)
139 : : {
140 : : if (target->ttype == PGPA_TARGET_IDENTIFIER ||
141 : : list_length(target->children) == 1)
142 : : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
143 : : "FOREIGN_JOIN targets must contain more than one relation identifier");
144 : : }
145 : : }
146 : :
147 : : $$->targets = $3;
148 : : }
149 : : ;
150 : :
151 : : relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
152 : : {
153 : : $$ = palloc0_object(pgpa_advice_target);
154 : : $$->ttype = PGPA_TARGET_IDENTIFIER;
155 : : $$->rid.alias_name = $1;
156 : : $$->rid.occurrence = $2;
157 : : if (list_length($3) == 2)
158 : : {
159 : : $$->rid.partnsp = linitial($3);
160 : : $$->rid.partrel = lsecond($3);
161 : : }
162 : : else if ($3 != NIL)
163 : : $$->rid.partrel = linitial($3);
164 : : $$->rid.plan_name = $4;
165 : : }
166 : : ;
167 : :
168 : : index_name: identifier
169 : : {
170 : : $$ = palloc0_object(pgpa_index_target);
171 : : $$->itype = PGPA_INDEX_NAME;
172 : : $$->indname = $1;
173 : : }
174 : : | identifier '.' identifier
175 : : {
176 : : $$ = palloc0_object(pgpa_index_target);
177 : : $$->itype = PGPA_INDEX_NAME;
178 : : $$->indnamespace = $1;
179 : : $$->indname = $3;
180 : : }
181 : : ;
182 : :
183 : : opt_ri_occurrence:
184 : : '#' TOK_INTEGER
185 : : {
186 : : if ($2 <= 0)
187 : : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
188 : : "only positive occurrence numbers are permitted");
189 : : $$ = $2;
190 : : }
191 : : |
192 : : {
193 : : /* The default occurrence number is 1. */
194 : : $$ = 1;
195 : : }
196 : : ;
197 : :
198 : : identifier: TOK_IDENT
199 : : | TOK_TAG_JOIN_ORDER
200 : : | TOK_TAG_INDEX
201 : : | TOK_TAG_BITMAP
202 : : | TOK_TAG_SIMPLE
203 : : | TOK_TAG_GENERIC
204 : : ;
205 : :
206 : : /*
207 : : * When generating advice, we always schema-qualify the partition name, but
208 : : * when parsing advice, we accept a specification that lacks one.
209 : : */
210 : : opt_partition:
211 : : '/' TOK_IDENT '.' TOK_IDENT
212 : : { $$ = list_make2($2, $4); }
213 : : | '/' TOK_IDENT
214 : : { $$ = list_make1($2); }
215 : : |
216 : : { $$ = NIL; }
217 : : ;
218 : :
219 : : opt_plan_name:
220 : : '@' TOK_IDENT
221 : : { $$ = $2; }
222 : : |
223 : : { $$ = NULL; }
224 : : ;
225 : :
226 : : bitmap_target_list: bitmap_target_list relation_identifier bitmap_target_item
227 : : {
228 : : $2->itarget = $3;
229 : : $$ = lappend($1, $2);
230 : : }
231 : : |
232 : : { $$ = NIL; }
233 : : ;
234 : :
235 : : bitmap_target_item: index_name
236 : : { $$ = $1; }
237 : : | TOK_OR '(' bitmap_sublist ')'
238 : : {
239 : : $$ = palloc0_object(pgpa_index_target);
240 : : $$->itype = PGPA_INDEX_OR;
241 : : $$->children = $3;
242 : : }
243 : : | TOK_AND '(' bitmap_sublist ')'
244 : : {
245 : : $$ = palloc0_object(pgpa_index_target);
246 : : $$->itype = PGPA_INDEX_AND;
247 : : $$->children = $3;
248 : : }
249 : : ;
250 : :
251 : : bitmap_sublist: bitmap_sublist bitmap_target_item
252 : : { $$ = lappend($1, $2); }
253 : : | bitmap_target_item
254 : : { $$ = list_make1($1); }
255 : : ;
256 : :
257 : : generic_target_list: generic_target_list relation_identifier
258 : : { $$ = lappend($1, $2); }
259 : : | generic_target_list generic_sublist
260 : : { $$ = lappend($1, $2); }
261 : : |
262 : : { $$ = NIL; }
263 : : ;
264 : :
265 : : generic_sublist: '(' simple_target_list ')'
266 : : {
267 : : $$ = palloc0_object(pgpa_advice_target);
268 : : $$->ttype = PGPA_TARGET_ORDERED_LIST;
269 : : $$->children = $2;
270 : : }
271 : : ;
272 : :
273 : : index_target_list:
274 : : index_target_list relation_identifier index_name
275 : : {
276 : : $2->itarget = $3;
277 : : $$ = lappend($1, $2);
278 : : }
279 : : |
280 : : { $$ = NIL; }
281 : : ;
282 : :
283 : : join_order_target_list: join_order_target_list relation_identifier
284 : : { $$ = lappend($1, $2); }
285 : : | join_order_target_list join_order_sublist
286 : : { $$ = lappend($1, $2); }
287 : : |
288 : : { $$ = NIL; }
289 : : ;
290 : :
291 : : join_order_sublist:
292 : : '(' join_order_target_list ')'
293 : : {
294 : : $$ = palloc0_object(pgpa_advice_target);
295 : : $$->ttype = PGPA_TARGET_ORDERED_LIST;
296 : : $$->children = $2;
297 : : }
298 : : | '{' simple_target_list '}'
299 : : {
300 : : $$ = palloc0_object(pgpa_advice_target);
301 : : $$->ttype = PGPA_TARGET_UNORDERED_LIST;
302 : : $$->children = $2;
303 : : }
304 : : ;
305 : :
306 : : simple_target_list: simple_target_list relation_identifier
307 : : { $$ = lappend($1, $2); }
308 : : |
309 : : { $$ = NIL; }
310 : : ;
311 : :
312 : : %%
313 : :
314 : : /*
315 : : * Parse an advice_string and return the resulting list of pgpa_advice_item
316 : : * objects. If a parse error occurs, instead return NULL.
317 : : *
318 : : * If the return value is NULL, *error_p will be set to the error message;
319 : : * otherwise, *error_p will be set to NULL.
320 : : */
321 : : List *
322 : 43647 : pgpa_parse(const char *advice_string, char **error_p)
323 : : {
324 : 43647 : yyscan_t scanner;
325 : 43647 : List *result;
326 : 43647 : char *error = NULL;
327 : :
328 : 43647 : pgpa_scanner_init(advice_string, &scanner);
329 : 43647 : pgpa_yyparse(&result, &error, scanner);
330 : 43647 : pgpa_scanner_finish(scanner);
331 : :
332 [ + + ]: 43647 : if (error != NULL)
333 : : {
334 : 17 : *error_p = error;
335 : 17 : return NULL;
336 : : }
337 : :
338 : 43630 : *error_p = NULL;
339 : 43630 : return result;
340 : 43647 : }
|