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 */
3 rhaas@postgresql.org 74 :GNC 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
3 rhaas@postgresql.org 101 [ # # ]:UNC 0 : elog(ERROR, "tag parsing failed: %s", $1);
3 rhaas@postgresql.org 102 :GNC 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
3 rhaas@postgresql.org 116 [ # # ]:UNC 0 : elog(ERROR, "tag parsing failed: %s", $1);
3 rhaas@postgresql.org 117 :GNC 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 : : {
3 rhaas@postgresql.org 127 :UNC 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
128 : : "unrecognized advice tag");
129 : : }
130 : :
3 rhaas@postgresql.org 131 [ + + ]:GNC 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)
3 rhaas@postgresql.org 180 :UNC 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
181 : : "only positive occurrence numbers are permitted");
3 rhaas@postgresql.org 182 :GNC 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 : : }
|