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 "common/string.h"
12 : #include "nodes/miscnodes.h"
13 : #include "parser/scansup.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 :
86 : space [ \t\n\r\f\v]
87 : whitespace {space}+
88 :
89 : dquote \"
90 : xdstart {dquote}
91 : xdstop {dquote}
92 : xddouble {dquote}{dquote}
93 : xdinside [^"]+
94 :
95 : xcstart \/\*
96 : xcstop \*+\/
97 : xcinside [^*/]+
98 :
99 : %%
100 :
101 : {whitespace} { /* ignore */ }
102 183 :
103 675 : {identifier} {
104 : char *str;
105 : bool fail;
106 : pgpa_advice_tag_type tag;
107 :
108 : /*
109 : * Unlike the core scanner, we don't truncate identifiers
110 : * here. There is no obvious reason to do so.
111 : */
112 675 : str = downcase_identifier(yytext, yyleng, false, false);
113 675 : yylval->str = str;
114 :
115 : /*
116 : * If it's not a tag, just return TOK_IDENT; else, return
117 : * a token type based on how further parsing should
118 : * proceed.
119 : */
120 675 : tag = pgpa_parse_advice_tag(str, &fail);
121 675 : if (fail)
122 418 : return TOK_IDENT;
123 257 : else if (tag == PGPA_TAG_JOIN_ORDER)
124 40 : return TOK_TAG_JOIN_ORDER;
125 217 : else if (tag == PGPA_TAG_INDEX_SCAN ||
126 : tag == PGPA_TAG_INDEX_ONLY_SCAN)
127 38 : return TOK_TAG_INDEX;
128 179 : else if (tag == PGPA_TAG_SEQ_SCAN ||
129 121 : tag == PGPA_TAG_TID_SCAN ||
130 115 : tag == PGPA_TAG_BITMAP_HEAP_SCAN ||
131 : tag == PGPA_TAG_NO_GATHER)
132 76 : return TOK_TAG_SIMPLE;
133 : else
134 103 : return TOK_TAG_GENERIC;
135 : }
136 :
137 9 : {decinteger} {
138 : char *endptr;
139 :
140 9 : errno = 0;
141 9 : yylval->integer = strtoint(yytext, &endptr, 10);
142 9 : if (*endptr != '\0' || errno == ERANGE)
143 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
144 : "integer out of range");
145 9 : return TOK_INTEGER;
146 : }
147 :
148 17 : {xcstart} {
149 17 : BEGIN(xc);
150 : }
151 17 :
152 20 : {xdstart} {
153 20 : BEGIN(xd);
154 20 : resetStringInfo(&yyextra->litbuf);
155 : }
156 20 :
157 634 : . { return yytext[0]; }
158 :
159 15 : <xc>{xcstop} {
160 15 : BEGIN(INITIAL);
161 : }
162 15 :
163 10 : <xc>{xcinside} {
164 : /* discard multiple characters without slash or asterisk */
165 : }
166 10 :
167 4 : <xc>. {
168 : /*
169 : * Discard any single character. flex prefers longer
170 : * matches, so this rule will never be picked when we could
171 : * have matched xcstop.
172 : *
173 : * NB: At present, we don't bother to support nested
174 : * C-style comments here, but this logic could be extended
175 : * if that restriction poses a problem.
176 : */
177 : }
178 4 :
179 2 : <xc><<EOF>> {
180 2 : BEGIN(INITIAL);
181 2 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
182 : "unterminated comment");
183 : }
184 2 :
185 19 : <xd>{xdstop} {
186 19 : BEGIN(INITIAL);
187 19 : if (yyextra->litbuf.len == 0)
188 1 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
189 : "zero-length delimited identifier");
190 19 : yylval->str = pstrdup(yyextra->litbuf.data);
191 19 : return TOK_IDENT;
192 : }
193 :
194 0 : <xd>{xddouble} {
195 0 : appendStringInfoChar(&yyextra->litbuf, '"');
196 : }
197 0 :
198 18 : <xd>{xdinside} {
199 18 : appendBinaryStringInfo(&yyextra->litbuf, yytext, yyleng);
200 : }
201 18 :
202 1 : <xd><<EOF>> {
203 1 : BEGIN(INITIAL);
204 1 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
205 : "unterminated quoted identifier");
206 : }
207 1 :
208 0 : %%
209 :
210 : /* LCOV_EXCL_STOP */
211 :
212 : /*
213 : * Handler for errors while scanning or parsing advice.
214 : *
215 : * bison passes the error message to us via 'message', and the context is
216 : * available via the 'yytext' macro. We assemble those values into a final
217 : * error text and then arrange to pass it back to the caller of pgpa_yyparse()
218 : * by storing it into *parse_error_msg_p.
219 : */
220 : void
221 18 : pgpa_yyerror(List **result, char **parse_error_msg_p, yyscan_t yyscanner,
222 : const char *message)
223 : {
224 18 : struct yyguts_t *yyg = (struct yyguts_t *) yyscanner; /* needed for yytext
225 : * macro */
226 :
227 :
228 : /* report only the first error in a parse operation */
229 18 : if (*parse_error_msg_p)
230 1 : return;
231 :
232 17 : if (yytext[0])
233 11 : *parse_error_msg_p = psprintf("%s at or near \"%s\"", message, yytext);
234 : else
235 6 : *parse_error_msg_p = psprintf("%s at end of input", message);
236 : }
237 :
238 : /*
239 : * Initialize the advice scanner.
240 : *
241 : * This should be called before parsing begins.
242 : */
243 : void
244 243 : pgpa_scanner_init(const char *str, yyscan_t *yyscannerp)
245 : {
246 : yyscan_t yyscanner;
247 243 : pgpa_yy_extra_type *yyext = palloc0_object(pgpa_yy_extra_type);
248 :
249 243 : if (yylex_init(yyscannerp) != 0)
250 0 : elog(ERROR, "yylex_init() failed: %m");
251 :
252 243 : yyscanner = *yyscannerp;
253 :
254 243 : initStringInfo(&yyext->litbuf);
255 243 : pgpa_yyset_extra(yyext, yyscanner);
256 :
257 243 : yy_scan_string(str, yyscanner);
258 243 : }
259 :
260 :
261 : /*
262 : * Shut down the advice scanner.
263 : *
264 : * This should be called after parsing is complete.
265 : */
266 : void
267 243 : pgpa_scanner_finish(yyscan_t yyscanner)
268 : {
269 243 : yylex_destroy(yyscanner);
270 243 : }
271 :
272 : /*
273 : * Interface functions to make flex use palloc() instead of malloc().
274 : * It'd be better to make these static, but flex insists otherwise.
275 : */
276 :
277 : void *
278 972 : yyalloc(yy_size_t size, yyscan_t yyscanner)
279 : {
280 972 : return palloc(size);
281 : }
282 :
283 : void *
284 0 : yyrealloc(void *ptr, yy_size_t size, yyscan_t yyscanner)
285 : {
286 0 : if (ptr)
287 0 : return repalloc(ptr, size);
288 : else
289 0 : return palloc(size);
290 : }
291 :
292 : void
293 1215 : yyfree(void *ptr, yyscan_t yyscanner)
294 : {
295 1215 : if (ptr)
296 972 : pfree(ptr);
297 1215 : }
|