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 43832 : *result = $1;
75 : }
76 : ;
77 :
78 : advice_item_list: advice_item_list advice_item
79 94741 : { $$ = lappend($1, $2); }
80 : |
81 43840 : { $$ = NIL; }
82 : ;
83 :
84 : advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
85 : {
86 10931 : $$ = palloc0_object(pgpa_advice_item);
87 10931 : $$->tag = PGPA_TAG_JOIN_ORDER;
88 10931 : $$->targets = $3;
89 10931 : 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 8714 : $$ = palloc0_object(pgpa_advice_item);
96 8714 : if (strcmp($1, "index_only_scan") == 0)
97 1715 : $$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
98 6999 : else if (strcmp($1, "index_scan") == 0)
99 6999 : $$->tag = PGPA_TAG_INDEX_SCAN;
100 : else
101 0 : elog(ERROR, "tag parsing failed: %s", $1);
102 8714 : $$->targets = $3;
103 : }
104 : | TOK_TAG_SIMPLE '(' simple_target_list ')'
105 : {
106 62717 : $$ = palloc0_object(pgpa_advice_item);
107 62717 : if (strcmp($1, "bitmap_heap_scan") == 0)
108 2333 : $$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
109 60384 : else if (strcmp($1, "do_not_scan") == 0)
110 180 : $$->tag = PGPA_TAG_DO_NOT_SCAN;
111 60204 : else if (strcmp($1, "no_gather") == 0)
112 43414 : $$->tag = PGPA_TAG_NO_GATHER;
113 16790 : else if (strcmp($1, "seq_scan") == 0)
114 16385 : $$->tag = PGPA_TAG_SEQ_SCAN;
115 405 : else if (strcmp($1, "tid_scan") == 0)
116 405 : $$->tag = PGPA_TAG_TID_SCAN;
117 : else
118 0 : elog(ERROR, "tag parsing failed: %s", $1);
119 62717 : $$->targets = $3;
120 : }
121 : | TOK_TAG_GENERIC '(' generic_target_list ')'
122 : {
123 : bool fail;
124 :
125 12379 : $$ = palloc0_object(pgpa_advice_item);
126 12379 : $$->tag = pgpa_parse_advice_tag($1, &fail);
127 12379 : if (fail)
128 : {
129 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
130 : "unrecognized advice tag");
131 : }
132 :
133 12379 : if ($$->tag == PGPA_TAG_FOREIGN_JOIN)
134 : {
135 12 : foreach_ptr(pgpa_advice_target, target, $3)
136 : {
137 7 : if (target->ttype == PGPA_TARGET_IDENTIFIER ||
138 3 : list_length(target->children) == 1)
139 2 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
140 : "FOREIGN_JOIN targets must contain more than one relation identifier");
141 : }
142 : }
143 :
144 12379 : $$->targets = $3;
145 : }
146 : ;
147 :
148 : relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
149 : {
150 158239 : $$ = palloc0_object(pgpa_advice_target);
151 158239 : $$->ttype = PGPA_TARGET_IDENTIFIER;
152 158239 : $$->rid.alias_name = $1;
153 158239 : $$->rid.occurrence = $2;
154 158239 : if (list_length($3) == 2)
155 : {
156 14211 : $$->rid.partnsp = linitial($3);
157 14211 : $$->rid.partrel = lsecond($3);
158 : }
159 144028 : else if ($3 != NIL)
160 14 : $$->rid.partrel = linitial($3);
161 158239 : $$->rid.plan_name = $4;
162 : }
163 : ;
164 :
165 : index_name: identifier
166 : {
167 35 : $$ = palloc0_object(pgpa_index_target);
168 35 : $$->indname = $1;
169 : }
170 : | identifier '.' identifier
171 : {
172 13463 : $$ = palloc0_object(pgpa_index_target);
173 13463 : $$->indnamespace = $1;
174 13463 : $$->indname = $3;
175 : }
176 : ;
177 :
178 : opt_ri_occurrence:
179 : '#' TOK_INTEGER
180 : {
181 3391 : if ($2 <= 0)
182 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
183 : "only positive occurrence numbers are permitted");
184 3391 : $$ = $2;
185 : }
186 : |
187 : {
188 : /* The default occurrence number is 1. */
189 154848 : $$ = 1;
190 : }
191 : ;
192 :
193 : identifier: TOK_IDENT
194 : | TOK_TAG_JOIN_ORDER
195 : | TOK_TAG_INDEX
196 : | TOK_TAG_SIMPLE
197 : | TOK_TAG_GENERIC
198 : ;
199 :
200 : /*
201 : * When generating advice, we always schema-qualify the partition name, but
202 : * when parsing advice, we accept a specification that lacks one.
203 : */
204 : opt_partition:
205 : '/' identifier '.' identifier
206 14211 : { $$ = list_make2($2, $4); }
207 : | '/' identifier
208 14 : { $$ = list_make1($2); }
209 : |
210 144014 : { $$ = NIL; }
211 : ;
212 :
213 : opt_plan_name:
214 : '@' identifier
215 36097 : { $$ = $2; }
216 : |
217 122142 : { $$ = NULL; }
218 : ;
219 :
220 : generic_target_list: generic_target_list relation_identifier
221 16800 : { $$ = lappend($1, $2); }
222 : | generic_target_list generic_sublist
223 850 : { $$ = lappend($1, $2); }
224 : |
225 12380 : { $$ = NIL; }
226 : ;
227 :
228 : generic_sublist: '(' simple_target_list ')'
229 : {
230 850 : $$ = palloc0_object(pgpa_advice_target);
231 850 : $$->ttype = PGPA_TARGET_ORDERED_LIST;
232 850 : $$->children = $2;
233 : }
234 : ;
235 :
236 : index_target_list:
237 : index_target_list relation_identifier index_name
238 : {
239 13498 : $2->itarget = $3;
240 13498 : $$ = lappend($1, $2);
241 : }
242 : |
243 8714 : { $$ = NIL; }
244 : ;
245 :
246 : join_order_target_list: join_order_target_list relation_identifier
247 25405 : { $$ = lappend($1, $2); }
248 : | join_order_target_list join_order_sublist
249 538 : { $$ = lappend($1, $2); }
250 : |
251 11454 : { $$ = NIL; }
252 : ;
253 :
254 : join_order_sublist:
255 : '(' join_order_target_list ')'
256 : {
257 523 : $$ = palloc0_object(pgpa_advice_target);
258 523 : $$->ttype = PGPA_TARGET_ORDERED_LIST;
259 523 : $$->children = $2;
260 : }
261 : | '{' simple_target_list '}'
262 : {
263 15 : $$ = palloc0_object(pgpa_advice_target);
264 15 : $$->ttype = PGPA_TARGET_UNORDERED_LIST;
265 15 : $$->children = $2;
266 : }
267 : ;
268 :
269 : simple_target_list: simple_target_list relation_identifier
270 102536 : { $$ = lappend($1, $2); }
271 : |
272 63589 : { $$ = NIL; }
273 : ;
274 :
275 : %%
276 :
277 : /*
278 : * Parse an advice_string and return the resulting list of pgpa_advice_item
279 : * objects. If a parse error occurs, instead return NULL.
280 : *
281 : * If the return value is NULL, *error_p will be set to the error message;
282 : * otherwise, *error_p will be set to NULL.
283 : */
284 : List *
285 43840 : pgpa_parse(const char *advice_string, char **error_p)
286 : {
287 : yyscan_t scanner;
288 : List *result;
289 43840 : char *error = NULL;
290 :
291 43840 : pgpa_scanner_init(advice_string, &scanner);
292 43840 : pgpa_yyparse(&result, &error, scanner);
293 43840 : pgpa_scanner_finish(scanner);
294 :
295 43840 : if (error != NULL)
296 : {
297 18 : *error_p = error;
298 18 : return NULL;
299 : }
300 :
301 43822 : *error_p = NULL;
302 43822 : return result;
303 : }
|