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 43645 : *result = $1;
75 : }
76 : ;
77 :
78 : advice_item_list: advice_item_list advice_item
79 95341 : { $$ = lappend($1, $2); }
80 : |
81 43652 : { $$ = NIL; }
82 : ;
83 :
84 : advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
85 : {
86 11158 : $$ = palloc0_object(pgpa_advice_item);
87 11158 : $$->tag = PGPA_TAG_JOIN_ORDER;
88 11158 : $$->targets = $3;
89 11158 : 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 8693 : $$ = palloc0_object(pgpa_advice_item);
96 8693 : if (strcmp($1, "index_only_scan") == 0)
97 1677 : $$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
98 7016 : else if (strcmp($1, "index_scan") == 0)
99 7016 : $$->tag = PGPA_TAG_INDEX_SCAN;
100 : else
101 0 : elog(ERROR, "tag parsing failed: %s", $1);
102 8693 : $$->targets = $3;
103 : }
104 : | TOK_TAG_SIMPLE '(' simple_target_list ')'
105 : {
106 62712 : $$ = palloc0_object(pgpa_advice_item);
107 62712 : if (strcmp($1, "bitmap_heap_scan") == 0)
108 2557 : $$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
109 60155 : else if (strcmp($1, "no_gather") == 0)
110 43236 : $$->tag = PGPA_TAG_NO_GATHER;
111 16919 : else if (strcmp($1, "seq_scan") == 0)
112 16516 : $$->tag = PGPA_TAG_SEQ_SCAN;
113 403 : else if (strcmp($1, "tid_scan") == 0)
114 403 : $$->tag = PGPA_TAG_TID_SCAN;
115 : else
116 0 : elog(ERROR, "tag parsing failed: %s", $1);
117 62712 : $$->targets = $3;
118 : }
119 : | TOK_TAG_GENERIC '(' generic_target_list ')'
120 : {
121 : bool fail;
122 :
123 12778 : $$ = palloc0_object(pgpa_advice_item);
124 12778 : $$->tag = pgpa_parse_advice_tag($1, &fail);
125 12778 : if (fail)
126 : {
127 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
128 : "unrecognized advice tag");
129 : }
130 :
131 12778 : 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 12778 : $$->targets = $3;
143 : }
144 : ;
145 :
146 : relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
147 : {
148 160168 : $$ = palloc0_object(pgpa_advice_target);
149 160168 : $$->ttype = PGPA_TARGET_IDENTIFIER;
150 160168 : $$->rid.alias_name = $1;
151 160168 : $$->rid.occurrence = $2;
152 160168 : if (list_length($3) == 2)
153 : {
154 14157 : $$->rid.partnsp = linitial($3);
155 14157 : $$->rid.partrel = lsecond($3);
156 : }
157 146011 : else if ($3 != NIL)
158 14 : $$->rid.partrel = linitial($3);
159 160168 : $$->rid.plan_name = $4;
160 : }
161 : ;
162 :
163 : index_name: identifier
164 : {
165 35 : $$ = palloc0_object(pgpa_index_target);
166 35 : $$->indname = $1;
167 : }
168 : | identifier '.' identifier
169 : {
170 13720 : $$ = palloc0_object(pgpa_index_target);
171 13720 : $$->indnamespace = $1;
172 13720 : $$->indname = $3;
173 : }
174 : ;
175 :
176 : opt_ri_occurrence:
177 : '#' TOK_INTEGER
178 : {
179 3373 : if ($2 <= 0)
180 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
181 : "only positive occurrence numbers are permitted");
182 3373 : $$ = $2;
183 : }
184 : |
185 : {
186 : /* The default occurrence number is 1. */
187 156795 : $$ = 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 : '/' identifier '.' identifier
204 14157 : { $$ = list_make2($2, $4); }
205 : | '/' identifier
206 14 : { $$ = list_make1($2); }
207 : |
208 145997 : { $$ = NIL; }
209 : ;
210 :
211 : opt_plan_name:
212 : '@' identifier
213 36539 : { $$ = $2; }
214 : |
215 123629 : { $$ = NULL; }
216 : ;
217 :
218 : generic_target_list: generic_target_list relation_identifier
219 17534 : { $$ = lappend($1, $2); }
220 : | generic_target_list generic_sublist
221 854 : { $$ = lappend($1, $2); }
222 : |
223 12779 : { $$ = NIL; }
224 : ;
225 :
226 : generic_sublist: '(' simple_target_list ')'
227 : {
228 854 : $$ = palloc0_object(pgpa_advice_target);
229 854 : $$->ttype = PGPA_TARGET_ORDERED_LIST;
230 854 : $$->children = $2;
231 : }
232 : ;
233 :
234 : index_target_list:
235 : index_target_list relation_identifier index_name
236 : {
237 13755 : $2->itarget = $3;
238 13755 : $$ = lappend($1, $2);
239 : }
240 : |
241 8693 : { $$ = NIL; }
242 : ;
243 :
244 : join_order_target_list: join_order_target_list relation_identifier
245 25971 : { $$ = lappend($1, $2); }
246 : | join_order_target_list join_order_sublist
247 542 : { $$ = lappend($1, $2); }
248 : |
249 11685 : { $$ = NIL; }
250 : ;
251 :
252 : join_order_sublist:
253 : '(' join_order_target_list ')'
254 : {
255 527 : $$ = palloc0_object(pgpa_advice_target);
256 527 : $$->ttype = PGPA_TARGET_ORDERED_LIST;
257 527 : $$->children = $2;
258 : }
259 : | '{' simple_target_list '}'
260 : {
261 15 : $$ = palloc0_object(pgpa_advice_target);
262 15 : $$->ttype = PGPA_TARGET_UNORDERED_LIST;
263 15 : $$->children = $2;
264 : }
265 : ;
266 :
267 : simple_target_list: simple_target_list relation_identifier
268 102908 : { $$ = lappend($1, $2); }
269 : |
270 63587 : { $$ = 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 43652 : pgpa_parse(const char *advice_string, char **error_p)
284 : {
285 : yyscan_t scanner;
286 : List *result;
287 43652 : char *error = NULL;
288 :
289 43652 : pgpa_scanner_init(advice_string, &scanner);
290 43652 : pgpa_yyparse(&result, &error, scanner);
291 43652 : pgpa_scanner_finish(scanner);
292 :
293 43652 : if (error != NULL)
294 : {
295 17 : *error_p = error;
296 17 : return NULL;
297 : }
298 :
299 43635 : *error_p = NULL;
300 43635 : return result;
301 : }
|