Line data Source code
1 : %{
2 : /*
3 : * Parser for plan advice
4 : *
5 : * Copyright (c) 2000-2026, 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_INDEX
53 : %token <str> TOK_TAG_SIMPLE TOK_TAG_GENERIC
54 : %token <integer> TOK_INTEGER
55 :
56 : %type <integer> opt_ri_occurrence
57 : %type <item> advice_item
58 : %type <list> advice_item_list generic_target_list
59 : %type <list> index_target_list join_order_target_list
60 : %type <list> opt_partition simple_target_list
61 : %type <str> identifier opt_plan_name
62 : %type <target> generic_sublist join_order_sublist
63 : %type <target> relation_identifier
64 : %type <itarget> index_name
65 :
66 : %start parse_toplevel
67 :
68 : /* Grammar follows */
69 : %%
70 :
71 : parse_toplevel: advice_item_list
72 : {
73 : (void) yynerrs; /* suppress compiler warning */
74 236 : *result = $1;
75 : }
76 : ;
77 :
78 : advice_item_list: advice_item_list advice_item
79 250 : { $$ = lappend($1, $2); }
80 : |
81 243 : { $$ = NIL; }
82 : ;
83 :
84 : advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
85 : {
86 40 : $$ = palloc0_object(pgpa_advice_item);
87 40 : $$->tag = PGPA_TAG_JOIN_ORDER;
88 40 : $$->targets = $3;
89 40 : if ($3 == NIL)
90 1 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
91 : "JOIN_ORDER must have at least one target");
92 : }
93 : | TOK_TAG_INDEX '(' index_target_list ')'
94 : {
95 38 : $$ = palloc0_object(pgpa_advice_item);
96 38 : if (strcmp($1, "index_only_scan") == 0)
97 12 : $$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
98 26 : else if (strcmp($1, "index_scan") == 0)
99 26 : $$->tag = PGPA_TAG_INDEX_SCAN;
100 : else
101 0 : elog(ERROR, "tag parsing failed: %s", $1);
102 38 : $$->targets = $3;
103 : }
104 : | TOK_TAG_SIMPLE '(' simple_target_list ')'
105 : {
106 70 : $$ = palloc0_object(pgpa_advice_item);
107 70 : if (strcmp($1, "bitmap_heap_scan") == 0)
108 6 : $$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
109 64 : else if (strcmp($1, "no_gather") == 0)
110 12 : $$->tag = PGPA_TAG_NO_GATHER;
111 52 : else if (strcmp($1, "seq_scan") == 0)
112 45 : $$->tag = PGPA_TAG_SEQ_SCAN;
113 7 : else if (strcmp($1, "tid_scan") == 0)
114 7 : $$->tag = PGPA_TAG_TID_SCAN;
115 : else
116 0 : elog(ERROR, "tag parsing failed: %s", $1);
117 70 : $$->targets = $3;
118 : }
119 : | TOK_TAG_GENERIC '(' generic_target_list ')'
120 : {
121 : bool fail;
122 :
123 102 : $$ = palloc0_object(pgpa_advice_item);
124 102 : $$->tag = pgpa_parse_advice_tag($1, &fail);
125 102 : if (fail)
126 : {
127 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
128 : "unrecognized advice tag");
129 : }
130 :
131 102 : if ($$->tag == PGPA_TAG_FOREIGN_JOIN)
132 : {
133 12 : foreach_ptr(pgpa_advice_target, target, $3)
134 : {
135 7 : if (target->ttype == PGPA_TARGET_IDENTIFIER ||
136 3 : list_length(target->children) == 1)
137 2 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
138 : "FOREIGN_JOIN targets must contain more than one relation identifier");
139 : }
140 : }
141 :
142 102 : $$->targets = $3;
143 : }
144 : ;
145 :
146 : relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
147 : {
148 338 : $$ = palloc0_object(pgpa_advice_target);
149 338 : $$->ttype = PGPA_TARGET_IDENTIFIER;
150 338 : $$->rid.alias_name = $1;
151 338 : $$->rid.occurrence = $2;
152 338 : if (list_length($3) == 2)
153 : {
154 12 : $$->rid.partnsp = linitial($3);
155 12 : $$->rid.partrel = lsecond($3);
156 : }
157 326 : else if ($3 != NIL)
158 14 : $$->rid.partrel = linitial($3);
159 338 : $$->rid.plan_name = $4;
160 : }
161 : ;
162 :
163 : index_name: identifier
164 : {
165 34 : $$ = palloc0_object(pgpa_index_target);
166 34 : $$->indname = $1;
167 : }
168 : | identifier '.' identifier
169 : {
170 8 : $$ = palloc0_object(pgpa_index_target);
171 8 : $$->indnamespace = $1;
172 8 : $$->indname = $3;
173 : }
174 : ;
175 :
176 : opt_ri_occurrence:
177 : '#' TOK_INTEGER
178 : {
179 8 : if ($2 <= 0)
180 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
181 : "only positive occurrence numbers are permitted");
182 8 : $$ = $2;
183 : }
184 : |
185 : {
186 : /* The default occurrence number is 1. */
187 330 : $$ = 1;
188 : }
189 : ;
190 :
191 : identifier: TOK_IDENT
192 : | TOK_TAG_JOIN_ORDER
193 : | TOK_TAG_INDEX
194 : | TOK_TAG_SIMPLE
195 : | TOK_TAG_GENERIC
196 : ;
197 :
198 : /*
199 : * When generating advice, we always schema-qualify the partition name, but
200 : * when parsing advice, we accept a specification that lacks one.
201 : */
202 : opt_partition:
203 : '/' TOK_IDENT '.' TOK_IDENT
204 12 : { $$ = list_make2($2, $4); }
205 : | '/' TOK_IDENT
206 14 : { $$ = list_make1($2); }
207 : |
208 312 : { $$ = NIL; }
209 : ;
210 :
211 : opt_plan_name:
212 : '@' TOK_IDENT
213 10 : { $$ = $2; }
214 : |
215 328 : { $$ = NULL; }
216 : ;
217 :
218 : generic_target_list: generic_target_list relation_identifier
219 91 : { $$ = lappend($1, $2); }
220 : | generic_target_list generic_sublist
221 23 : { $$ = lappend($1, $2); }
222 : |
223 103 : { $$ = NIL; }
224 : ;
225 :
226 : generic_sublist: '(' simple_target_list ')'
227 : {
228 23 : $$ = palloc0_object(pgpa_advice_target);
229 23 : $$->ttype = PGPA_TARGET_ORDERED_LIST;
230 23 : $$->children = $2;
231 : }
232 : ;
233 :
234 : index_target_list:
235 : index_target_list relation_identifier index_name
236 : {
237 42 : $2->itarget = $3;
238 42 : $$ = lappend($1, $2);
239 : }
240 : |
241 38 : { $$ = NIL; }
242 : ;
243 :
244 : join_order_target_list: join_order_target_list relation_identifier
245 85 : { $$ = lappend($1, $2); }
246 : | join_order_target_list join_order_sublist
247 6 : { $$ = lappend($1, $2); }
248 : |
249 44 : { $$ = NIL; }
250 : ;
251 :
252 : join_order_sublist:
253 : '(' join_order_target_list ')'
254 : {
255 4 : $$ = palloc0_object(pgpa_advice_target);
256 4 : $$->ttype = PGPA_TARGET_ORDERED_LIST;
257 4 : $$->children = $2;
258 : }
259 : | '{' simple_target_list '}'
260 : {
261 2 : $$ = palloc0_object(pgpa_advice_target);
262 2 : $$->ttype = PGPA_TARGET_UNORDERED_LIST;
263 2 : $$->children = $2;
264 : }
265 : ;
266 :
267 : simple_target_list: simple_target_list relation_identifier
268 120 : { $$ = lappend($1, $2); }
269 : |
270 101 : { $$ = NIL; }
271 : ;
272 :
273 : %%
274 :
275 : /*
276 : * Parse an advice_string and return the resulting list of pgpa_advice_item
277 : * objects. If a parse error occurs, instead return NULL.
278 : *
279 : * If the return value is NULL, *error_p will be set to the error message;
280 : * otherwise, *error_p will be set to NULL.
281 : */
282 : List *
283 243 : pgpa_parse(const char *advice_string, char **error_p)
284 : {
285 : yyscan_t scanner;
286 : List *result;
287 243 : char *error = NULL;
288 :
289 243 : pgpa_scanner_init(advice_string, &scanner);
290 243 : pgpa_yyparse(&result, &error, scanner);
291 243 : pgpa_scanner_finish(scanner);
292 :
293 243 : if (error != NULL)
294 : {
295 17 : *error_p = error;
296 17 : return NULL;
297 : }
298 :
299 226 : *error_p = NULL;
300 226 : return result;
301 : }
|