Age Owner Branch data TLA 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 */
79 rhaas@postgresql.org 74 :GNC 43821 : *result = $1;
75 : : }
76 : : ;
77 : :
78 : : advice_item_list: advice_item_list advice_item
79 : 95743 : { $$ = lappend($1, $2); }
80 : : |
81 : 43829 : { $$ = NIL; }
82 : : ;
83 : :
84 : : advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
85 : : {
86 : 11195 : $$ = palloc0_object(pgpa_advice_item);
87 : 11195 : $$->tag = PGPA_TAG_JOIN_ORDER;
88 : 11195 : $$->targets = $3;
89 [ + + ]: 11195 : 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 : 8960 : $$ = palloc0_object(pgpa_advice_item);
96 [ + + ]: 8960 : if (strcmp($1, "index_only_scan") == 0)
97 : 1708 : $$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
98 [ + - ]: 7252 : else if (strcmp($1, "index_scan") == 0)
99 : 7252 : $$->tag = PGPA_TAG_INDEX_SCAN;
100 : : else
79 rhaas@postgresql.org 101 [ # # ]:UNC 0 : elog(ERROR, "tag parsing failed: %s", $1);
79 rhaas@postgresql.org 102 :GNC 8960 : $$->targets = $3;
103 : : }
104 : : | TOK_TAG_SIMPLE '(' simple_target_list ')'
105 : : {
106 : 62963 : $$ = palloc0_object(pgpa_advice_item);
107 [ + + ]: 62963 : if (strcmp($1, "bitmap_heap_scan") == 0)
108 : 2311 : $$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
1 109 [ + + ]: 60652 : else if (strcmp($1, "do_not_scan") == 0)
110 : 231 : $$->tag = PGPA_TAG_DO_NOT_SCAN;
79 111 [ + + ]: 60421 : else if (strcmp($1, "no_gather") == 0)
112 : 43404 : $$->tag = PGPA_TAG_NO_GATHER;
113 [ + + ]: 17017 : else if (strcmp($1, "seq_scan") == 0)
114 : 16612 : $$->tag = PGPA_TAG_SEQ_SCAN;
115 [ + - ]: 405 : else if (strcmp($1, "tid_scan") == 0)
116 : 405 : $$->tag = PGPA_TAG_TID_SCAN;
117 : : else
79 rhaas@postgresql.org 118 [ # # ]:UNC 0 : elog(ERROR, "tag parsing failed: %s", $1);
79 rhaas@postgresql.org 119 :GNC 62963 : $$->targets = $3;
120 : : }
121 : : | TOK_TAG_GENERIC '(' generic_target_list ')'
122 : : {
123 : : bool fail;
124 : :
125 : 12625 : $$ = palloc0_object(pgpa_advice_item);
126 : 12625 : $$->tag = pgpa_parse_advice_tag($1, &fail);
127 [ - + ]: 12625 : if (fail)
128 : : {
79 rhaas@postgresql.org 129 :UNC 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
130 : : "unrecognized advice tag");
131 : : }
132 : :
79 rhaas@postgresql.org 133 [ + + ]:GNC 12625 : 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 : 12625 : $$->targets = $3;
145 : : }
146 : : ;
147 : :
148 : : relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
149 : : {
150 : 160764 : $$ = palloc0_object(pgpa_advice_target);
151 : 160764 : $$->ttype = PGPA_TARGET_IDENTIFIER;
152 : 160764 : $$->rid.alias_name = $1;
153 : 160764 : $$->rid.occurrence = $2;
154 [ + + ]: 160764 : if (list_length($3) == 2)
155 : : {
156 : 14173 : $$->rid.partnsp = linitial($3);
157 : 14173 : $$->rid.partrel = lsecond($3);
158 : : }
159 [ + + ]: 146591 : else if ($3 != NIL)
160 : 14 : $$->rid.partrel = linitial($3);
161 : 160764 : $$->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 : 13838 : $$ = palloc0_object(pgpa_index_target);
173 : 13838 : $$->indnamespace = $1;
174 : 13838 : $$->indname = $3;
175 : : }
176 : : ;
177 : :
178 : : opt_ri_occurrence:
179 : : '#' TOK_INTEGER
180 : : {
181 [ - + ]: 3402 : if ($2 <= 0)
79 rhaas@postgresql.org 182 :UNC 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
183 : : "only positive occurrence numbers are permitted");
79 rhaas@postgresql.org 184 :GNC 3402 : $$ = $2;
185 : : }
186 : : |
187 : : {
188 : : /* The default occurrence number is 1. */
189 : 157362 : $$ = 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 : 14173 : { $$ = list_make2($2, $4); }
207 : : | '/' identifier
208 : 14 : { $$ = list_make1($2); }
209 : : |
210 : 146577 : { $$ = NIL; }
211 : : ;
212 : :
213 : : opt_plan_name:
214 : : '@' identifier
215 : 36675 : { $$ = $2; }
216 : : |
217 : 124089 : { $$ = NULL; }
218 : : ;
219 : :
220 : : generic_target_list: generic_target_list relation_identifier
221 : 17172 : { $$ = lappend($1, $2); }
222 : : | generic_target_list generic_sublist
223 : 855 : { $$ = lappend($1, $2); }
224 : : |
225 : 12626 : { $$ = NIL; }
226 : : ;
227 : :
228 : : generic_sublist: '(' simple_target_list ')'
229 : : {
230 : 855 : $$ = palloc0_object(pgpa_advice_target);
231 : 855 : $$->ttype = PGPA_TARGET_ORDERED_LIST;
232 : 855 : $$->children = $2;
233 : : }
234 : : ;
235 : :
236 : : index_target_list:
237 : : index_target_list relation_identifier index_name
238 : : {
239 : 13873 : $2->itarget = $3;
240 : 13873 : $$ = lappend($1, $2);
241 : : }
242 : : |
243 : 8960 : { $$ = NIL; }
244 : : ;
245 : :
246 : : join_order_target_list: join_order_target_list relation_identifier
247 : 26053 : { $$ = lappend($1, $2); }
248 : : | join_order_target_list join_order_sublist
249 : 543 : { $$ = lappend($1, $2); }
250 : : |
251 : 11723 : { $$ = NIL; }
252 : : ;
253 : :
254 : : join_order_sublist:
255 : : '(' join_order_target_list ')'
256 : : {
257 : 528 : $$ = palloc0_object(pgpa_advice_target);
258 : 528 : $$->ttype = PGPA_TARGET_ORDERED_LIST;
259 : 528 : $$->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 : 103666 : { $$ = lappend($1, $2); }
271 : : |
272 : 63840 : { $$ = 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 : 43829 : pgpa_parse(const char *advice_string, char **error_p)
286 : : {
287 : : yyscan_t scanner;
288 : : List *result;
289 : 43829 : char *error = NULL;
290 : :
291 : 43829 : pgpa_scanner_init(advice_string, &scanner);
292 : 43829 : pgpa_yyparse(&result, &error, scanner);
293 : 43829 : pgpa_scanner_finish(scanner);
294 : :
295 [ + + ]: 43829 : if (error != NULL)
296 : : {
297 : 18 : *error_p = error;
298 : 18 : return NULL;
299 : : }
300 : :
301 : 43811 : *error_p = NULL;
302 : 43811 : return result;
303 : : }
|