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 */
192 rhaas@postgresql.org 74 :CBC 43353 : *result = $1;
75 : : }
76 : : ;
77 : :
78 : : advice_item_list: advice_item_list advice_item
79 : 94823 : { $$ = lappend($1, $2); }
80 : : |
81 : 43363 : { $$ = NIL; }
82 : : ;
83 : :
84 : : advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
85 : : {
86 : 10977 : $$ = palloc0_object(pgpa_advice_item);
87 : 10977 : $$->tag = PGPA_TAG_JOIN_ORDER;
88 : 10977 : $$->targets = $3;
89 [ + + ]: 10977 : 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 : 9257 : $$ = palloc0_object(pgpa_advice_item);
96 [ + + ]: 9257 : if (strcmp($1, "index_only_scan") == 0)
97 : 1474 : $$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
98 [ + - ]: 7783 : else if (strcmp($1, "index_scan") == 0)
99 : 7783 : $$->tag = PGPA_TAG_INDEX_SCAN;
100 : : else
192 rhaas@postgresql.org 101 [ # # ]:UBC 0 : elog(ERROR, "tag parsing failed: %s", $1);
192 rhaas@postgresql.org 102 :CBC 9257 : $$->targets = $3;
103 : : }
104 : : | TOK_TAG_SIMPLE '(' simple_target_list ')'
105 : : {
106 : 62190 : $$ = palloc0_object(pgpa_advice_item);
107 [ + + ]: 62190 : if (strcmp($1, "bitmap_heap_scan") == 0)
108 : 2173 : $$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
114 109 [ + + ]: 60017 : else if (strcmp($1, "do_not_scan") == 0)
110 : 233 : $$->tag = PGPA_TAG_DO_NOT_SCAN;
192 111 [ + + ]: 59784 : else if (strcmp($1, "no_gather") == 0)
112 : 42921 : $$->tag = PGPA_TAG_NO_GATHER;
113 [ + + ]: 16863 : else if (strcmp($1, "seq_scan") == 0)
114 : 16459 : $$->tag = PGPA_TAG_SEQ_SCAN;
115 [ + - ]: 404 : else if (strcmp($1, "tid_scan") == 0)
116 : 404 : $$->tag = PGPA_TAG_TID_SCAN;
117 : : else
192 rhaas@postgresql.org 118 [ # # ]:UBC 0 : elog(ERROR, "tag parsing failed: %s", $1);
192 rhaas@postgresql.org 119 :CBC 62190 : $$->targets = $3;
120 : : }
121 : : | TOK_TAG_GENERIC '(' generic_target_list ')'
122 : : {
123 : : bool fail;
124 : :
125 : 12399 : $$ = palloc0_object(pgpa_advice_item);
126 : 12399 : $$->tag = pgpa_parse_advice_tag($1, &fail);
127 [ - + ]: 12399 : if (fail)
128 : : {
192 rhaas@postgresql.org 129 :UBC 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
130 : : "unrecognized advice tag");
131 : : }
132 : :
192 rhaas@postgresql.org 133 [ + + ]:CBC 12399 : if ($$->tag == PGPA_TAG_FOREIGN_JOIN)
134 : : {
135 [ + - + + : 15 : foreach_ptr(pgpa_advice_target, target, $3)
+ + ]
136 : : {
137 [ + + + + ]: 9 : if (target->ttype == PGPA_TARGET_IDENTIFIER ||
24 138 : 4 : list_length(target->children) < 2)
192 139 : 3 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
140 : : "FOREIGN_JOIN targets must contain more than one relation identifier");
141 : : }
142 : : }
143 : :
144 : 12399 : $$->targets = $3;
145 : : }
146 : : ;
147 : :
148 : : relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
149 : : {
150 : 159183 : $$ = palloc0_object(pgpa_advice_target);
151 : 159183 : $$->ttype = PGPA_TARGET_IDENTIFIER;
152 : 159183 : $$->rid.alias_name = $1;
153 : 159183 : $$->rid.occurrence = $2;
4 154 [ + + ]: 159183 : if ($3 != NIL)
155 : : {
156 [ - + ]: 13847 : Assert(list_length($3) == 2);
192 157 : 13847 : $$->rid.partnsp = linitial($3);
158 : 13847 : $$->rid.partrel = lsecond($3);
159 : : }
160 : 159183 : $$->rid.plan_name = $4;
161 : : }
162 : : ;
163 : :
164 : : index_name: identifier
165 : : {
166 : 35 : $$ = palloc0_object(pgpa_index_target);
167 : 35 : $$->indname = $1;
168 : : }
169 : : | identifier '.' identifier
170 : : {
171 : 14650 : $$ = palloc0_object(pgpa_index_target);
172 : 14650 : $$->indnamespace = $1;
173 : 14650 : $$->indname = $3;
174 : : }
175 : : ;
176 : :
177 : : opt_ri_occurrence:
178 : : '#' TOK_INTEGER
179 : : {
180 [ + + ]: 3085 : if ($2 <= 0)
181 : 1 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
182 : : "only positive occurrence numbers are permitted");
183 : 3085 : $$ = $2;
184 : : }
185 : : |
186 : : {
187 : : /* The default occurrence number is 1. */
188 : 156099 : $$ = 1;
189 : : }
190 : : ;
191 : :
192 : : identifier: TOK_IDENT
193 : : | TOK_TAG_JOIN_ORDER
194 : : | TOK_TAG_INDEX
195 : : | TOK_TAG_SIMPLE
196 : : | TOK_TAG_GENERIC
197 : : ;
198 : :
199 : : /*
200 : : * The partition name must always be schema-qualified. Otherwise, a relation
201 : : * identifier could refer to more than one partition of the same partitioned
202 : : * table, and relation identifiers are required to be unique.
203 : : */
204 : : opt_partition:
205 : : '/' identifier '.' identifier
206 : 13847 : { $$ = list_make2($2, $4); }
207 : : |
208 : 145336 : { $$ = NIL; }
209 : : ;
210 : :
211 : : opt_plan_name:
212 : : '@' identifier
213 : 36665 : { $$ = $2; }
214 : : |
215 : 122518 : { $$ = NULL; }
216 : : ;
217 : :
218 : : generic_target_list: generic_target_list relation_identifier
219 : 17194 : { $$ = lappend($1, $2); }
220 : : | generic_target_list generic_sublist
221 : 764 : { $$ = lappend($1, $2); }
222 : : |
223 : 12400 : { $$ = NIL; }
224 : : ;
225 : :
226 : : generic_sublist: '(' simple_target_list ')'
227 : : {
228 : 764 : $$ = palloc0_object(pgpa_advice_target);
229 : 764 : $$->ttype = PGPA_TARGET_ORDERED_LIST;
230 : 764 : $$->children = $2;
231 : : }
232 : : ;
233 : :
234 : : index_target_list:
235 : : index_target_list relation_identifier index_name
236 : : {
237 : 14685 : $2->itarget = $3;
238 : 14685 : $$ = lappend($1, $2);
239 : : }
240 : : |
241 : 9257 : { $$ = NIL; }
242 : : ;
243 : :
244 : : join_order_target_list: join_order_target_list relation_identifier
245 : 25876 : { $$ = lappend($1, $2); }
246 : : | join_order_target_list join_order_sublist
247 : 468 : { $$ = lappend($1, $2); }
248 : : |
249 : 11419 : { $$ = NIL; }
250 : : ;
251 : :
252 : : join_order_sublist:
253 : : '(' join_order_target_list ')'
254 : : {
255 : 442 : $$ = palloc0_object(pgpa_advice_target);
256 : 442 : $$->ttype = PGPA_TARGET_ORDERED_LIST;
257 : 442 : $$->children = $2;
4 258 [ + + ]: 442 : if ($2 == NIL)
259 : 1 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
260 : : "JOIN_ORDER targets must contain at least one relation identifier");
261 : : }
262 : : | '{' simple_target_list '}'
263 : : {
192 264 : 26 : $$ = palloc0_object(pgpa_advice_target);
265 : 26 : $$->ttype = PGPA_TARGET_UNORDERED_LIST;
266 : 26 : $$->children = $2;
4 267 [ + + ]: 26 : if ($2 == NIL)
268 : 1 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
269 : : "JOIN_ORDER targets must contain at least one relation identifier");
270 : : }
271 : : ;
272 : :
273 : : simple_target_list: simple_target_list relation_identifier
192 274 : 101428 : { $$ = lappend($1, $2); }
275 : : |
276 : 62989 : { $$ = NIL; }
277 : : ;
278 : :
279 : : %%
280 : :
281 : : /*
282 : : * Parse an advice_string and return the resulting list of pgpa_advice_item
283 : : * objects. If a parse error occurs, instead return NULL.
284 : : *
285 : : * If the return value is NULL, *error_p will be set to the error message;
286 : : * otherwise, *error_p will be set to NULL.
287 : : */
288 : : List *
289 : 43363 : pgpa_parse(const char *advice_string, char **error_p)
290 : : {
291 : : yyscan_t scanner;
292 : : List *result;
293 : 43363 : char *error = NULL;
294 : :
295 : 43363 : pgpa_scanner_init(advice_string, &scanner);
296 : 43363 : pgpa_yyparse(&result, &error, scanner);
297 : 43363 : pgpa_scanner_finish(scanner);
298 : :
299 [ + + ]: 43363 : if (error != NULL)
300 : : {
301 : 24 : *error_p = error;
302 : 24 : return NULL;
303 : : }
304 : :
305 : 43339 : *error_p = NULL;
306 : 43339 : return result;
307 : : }
|