Branch data Line data Source code
1 : : %top{
2 : : /*
3 : : * Scanner for plan advice
4 : : *
5 : : * Copyright (c) 2000-2026, PostgreSQL Global Development Group
6 : : *
7 : : * contrib/pg_plan_advice/pgpa_scanner.l
8 : : */
9 : : #include "postgres.h"
10 : :
11 : : #include "nodes/miscnodes.h"
12 : : #include "parser/scansup.h"
13 : : #include "utils/builtins.h"
14 : :
15 : : #include "pgpa_ast.h"
16 : : #include "pgpa_parser.h"
17 : :
18 : : /*
19 : : * Extra data that we pass around when during scanning.
20 : : *
21 : : * 'litbuf' is used to implement the <xd> exclusive state, which handles
22 : : * double-quoted identifiers.
23 : : */
24 : : typedef struct pgpa_yy_extra_type
25 : : {
26 : : StringInfoData litbuf;
27 : : } pgpa_yy_extra_type;
28 : :
29 : : }
30 : :
31 : : %{
32 : : /* LCOV_EXCL_START */
33 : :
34 : : #define YY_DECL \
35 : : extern int pgpa_yylex(union YYSTYPE *yylval_param, List **result, \
36 : : char **parse_error_msg_p, yyscan_t yyscanner)
37 : :
38 : : /* No reason to constrain amount of data slurped */
39 : : #define YY_READ_BUF_SIZE 16777216
40 : :
41 : : /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */
42 : : #undef fprintf
43 : : #define fprintf(file, fmt, msg) fprintf_to_ereport(fmt, msg)
44 : :
45 : : static void
46 : 0 : fprintf_to_ereport(const char *fmt, const char *msg)
47 : : {
48 [ # # ]: 0 : ereport(ERROR, (errmsg_internal("%s", msg)));
49 : : }
50 : : %}
51 : :
52 : : %option reentrant
53 : : %option bison-bridge
54 : : %option 8bit
55 : : %option never-interactive
56 : : %option nodefault
57 : : %option noinput
58 : : %option nounput
59 : : %option noyywrap
60 : : %option noyyalloc
61 : : %option noyyrealloc
62 : : %option noyyfree
63 : : %option warn
64 : : %option prefix="pgpa_yy"
65 : : %option extra-type="pgpa_yy_extra_type *"
66 : :
67 : : /*
68 : : * What follows is a severely stripped-down version of the core scanner. We
69 : : * only care about recognizing identifiers with or without identifier quoting
70 : : * (i.e. double-quoting), decimal integers, and a small handful of other
71 : : * things. Keep these rules in sync with src/backend/parser/scan.l. As in that
72 : : * file, we use an exclusive state called 'xc' for C-style comments, and an
73 : : * exclusive state called 'xd' for double-quoted identifiers.
74 : : */
75 : : %x xc
76 : : %x xd
77 : :
78 : : ident_start [A-Za-z\200-\377_]
79 : : ident_cont [A-Za-z\200-\377_0-9\$]
80 : :
81 : : identifier {ident_start}{ident_cont}*
82 : :
83 : : decdigit [0-9]
84 : : decinteger {decdigit}(_?{decdigit})*
85 : : integer_junk {decinteger}{identifier}
86 : :
87 : : space [ \t\n\r\f\v]
88 : : whitespace {space}+
89 : :
90 : : dquote \"
91 : : xdstart {dquote}
92 : : xdstop {dquote}
93 : : xddouble {dquote}{dquote}
94 : : xdinside [^"]+
95 : :
96 : : xcstart \/\*
97 : : xcstop \*+\/
98 : : xcinside [^*/]+
99 : :
100 : : %%
101 : :
102 : : {whitespace} { /* ignore */ }
103 : 131774 :
104 : 332063 : {identifier} {
105 : : char *str;
106 : : bool fail;
107 : : pgpa_advice_tag_type tag;
108 : :
109 : : /*
110 : : * Unlike the core scanner, we don't truncate identifiers
111 : : * here. There is no obvious reason to do so.
112 : : */
113 : 332063 : str = downcase_identifier(yytext, yyleng, false, false);
114 : 332063 : yylval->str = str;
115 : :
116 : : /*
117 : : * If it's not a tag, just return TOK_IDENT; else, return
118 : : * a token type based on how further parsing should
119 : : * proceed.
120 : : */
121 : 332063 : tag = pgpa_parse_advice_tag(str, &fail);
122 [ + + ]: 332063 : if (fail)
123 : 236513 : return TOK_IDENT;
124 [ + + ]: 95550 : else if (tag == PGPA_TAG_JOIN_ORDER)
125 : 10983 : return TOK_TAG_JOIN_ORDER;
126 [ + + + + ]: 84567 : else if (tag == PGPA_TAG_INDEX_SCAN ||
127 : : tag == PGPA_TAG_INDEX_ONLY_SCAN)
128 : 9045 : return TOK_TAG_INDEX;
129 [ + + + + ]: 75522 : else if (tag == PGPA_TAG_SEQ_SCAN ||
130 [ + + ]: 58695 : tag == PGPA_TAG_TID_SCAN ||
131 [ + + ]: 56119 : tag == PGPA_TAG_BITMAP_HEAP_SCAN ||
132 [ + + ]: 12634 : tag == PGPA_TAG_NO_GATHER ||
133 : : tag == PGPA_TAG_DO_NOT_SCAN)
134 : 63070 : return TOK_TAG_SIMPLE;
135 : : else
136 : 12452 : return TOK_TAG_GENERIC;
137 : : }
138 : :
139 : 3436 : {decinteger} {
140 : 3436 : ErrorSaveContext escontext = {T_ErrorSaveContext};
141 : :
142 : 3436 : yylval->integer = pg_strtoint32_safe(yytext,
143 : : (Node *) &escontext);
144 [ + + ]: 3436 : if (escontext.error_occurred)
145 : 1 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
146 : : "integer out of range");
147 : 3436 : return TOK_INTEGER;
148 : : }
149 : :
150 : 1 : {integer_junk} {
151 : 1 : BEGIN(INITIAL);
152 : 1 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
153 : : "trailing junk after numeric literal");
154 : : }
155 : 1 :
156 : 17 : {xcstart} {
157 : 17 : BEGIN(xc);
158 : : }
159 : 17 :
160 : 20466 : {xdstart} {
161 : 20466 : BEGIN(xd);
162 : 20466 : resetStringInfo(&yyextra->litbuf);
163 : : }
164 : 20466 :
165 : 278661 : . { return yytext[0]; }
166 : :
167 : 15 : <xc>{xcstop} {
168 : 15 : BEGIN(INITIAL);
169 : : }
170 : 15 :
171 : 10 : <xc>{xcinside} {
172 : : /* discard multiple characters without slash or asterisk */
173 : : }
174 : 10 :
175 : 4 : <xc>. {
176 : : /*
177 : : * Discard any single character. flex prefers longer
178 : : * matches, so this rule will never be picked when we could
179 : : * have matched xcstop.
180 : : *
181 : : * NB: At present, we don't bother to support nested
182 : : * C-style comments here, but this logic could be extended
183 : : * if that restriction poses a problem.
184 : : */
185 : : }
186 : 4 :
187 : 2 : <xc><<EOF>> {
188 : 2 : BEGIN(INITIAL);
189 : 2 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
190 : : "unterminated comment");
191 : : }
192 : 2 :
193 : 20465 : <xd>{xdstop} {
194 : 20465 : BEGIN(INITIAL);
195 [ + + ]: 20465 : if (yyextra->litbuf.len == 0)
196 : 1 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
197 : : "zero-length delimited identifier");
198 : 20465 : yylval->str = pstrdup(yyextra->litbuf.data);
199 : 20465 : return TOK_IDENT;
200 : : }
201 : :
202 : 0 : <xd>{xddouble} {
203 : 0 : appendStringInfoChar(&yyextra->litbuf, '"');
204 : : }
205 : 0 :
206 : 20464 : <xd>{xdinside} {
207 : 20464 : appendBinaryStringInfo(&yyextra->litbuf, yytext, yyleng);
208 : : }
209 : 20464 :
210 : 1 : <xd><<EOF>> {
211 : 1 : BEGIN(INITIAL);
212 : 1 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
213 : : "unterminated quoted identifier");
214 : : }
215 : 1 :
216 : 0 : %%
217 : :
218 : : /* LCOV_EXCL_STOP */
219 : :
220 : : /*
221 : : * Handler for errors while scanning or parsing advice.
222 : : *
223 : : * bison passes the error message to us via 'message', and the context is
224 : : * available via the 'yytext' macro. We assemble those values into a final
225 : : * error text and then arrange to pass it back to the caller of pgpa_yyparse()
226 : : * by storing it into *parse_error_msg_p.
227 : : */
228 : : void
229 : 23 : pgpa_yyerror(List **result, char **parse_error_msg_p, yyscan_t yyscanner,
230 : : const char *message)
231 : : {
232 : 23 : struct yyguts_t *yyg = (struct yyguts_t *) yyscanner; /* needed for yytext
233 : : * macro */
234 : :
235 : :
236 : : /* report only the first error in a parse operation */
237 [ + + ]: 23 : if (*parse_error_msg_p)
238 : 3 : return;
239 : :
240 [ + + ]: 20 : if (yytext[0])
241 : 14 : *parse_error_msg_p = psprintf("%s at or near \"%s\"", message, yytext);
242 : : else
243 : 6 : *parse_error_msg_p = psprintf("%s at end of input", message);
244 : : }
245 : :
246 : : /*
247 : : * Initialize the advice scanner.
248 : : *
249 : : * This should be called before parsing begins.
250 : : */
251 : : void
252 : 43915 : pgpa_scanner_init(const char *str, yyscan_t *yyscannerp)
253 : : {
254 : : yyscan_t yyscanner;
255 : 43915 : pgpa_yy_extra_type *yyext = palloc0_object(pgpa_yy_extra_type);
256 : :
257 [ - + ]: 43915 : if (yylex_init(yyscannerp) != 0)
258 [ # # ]: 0 : elog(ERROR, "yylex_init() failed: %m");
259 : :
260 : 43915 : yyscanner = *yyscannerp;
261 : :
262 : 43915 : initStringInfo(&yyext->litbuf);
263 : 43915 : pgpa_yyset_extra(yyext, yyscanner);
264 : :
265 : 43915 : yy_scan_string(str, yyscanner);
266 : 43915 : }
267 : :
268 : :
269 : : /*
270 : : * Shut down the advice scanner.
271 : : *
272 : : * This should be called after parsing is complete.
273 : : */
274 : : void
275 : 43915 : pgpa_scanner_finish(yyscan_t yyscanner)
276 : : {
277 : 43915 : yylex_destroy(yyscanner);
278 : 43915 : }
279 : :
280 : : /*
281 : : * Interface functions to make flex use palloc() instead of malloc().
282 : : * It'd be better to make these static, but flex insists otherwise.
283 : : */
284 : :
285 : : void *
286 : 175660 : yyalloc(yy_size_t size, yyscan_t yyscanner)
287 : : {
288 : 175660 : return palloc(size);
289 : : }
290 : :
291 : : void *
292 : 0 : yyrealloc(void *ptr, yy_size_t size, yyscan_t yyscanner)
293 : : {
294 [ # # ]: 0 : if (ptr)
295 : 0 : return repalloc(ptr, size);
296 : : else
297 : 0 : return palloc(size);
298 : : }
299 : :
300 : : void
301 : 219575 : yyfree(void *ptr, yyscan_t yyscanner)
302 : : {
303 [ + + ]: 219575 : if (ptr)
304 : 175660 : pfree(ptr);
305 : 219575 : }
|