Branch data Line data Source code
1 : : %top{
2 : : /*
3 : : * Scanner for plan advice
4 : : *
5 : : * Copyright (c) 2000-2025, 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 : 0 : }
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 : :
103 : : {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 : : str = downcase_identifier(yytext, yyleng, false, false);
113 : : 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 : : tag = pgpa_parse_advice_tag(str, &fail);
121 : : if (fail)
122 : : return TOK_IDENT;
123 : : else if (tag == PGPA_TAG_JOIN_ORDER)
124 : : return TOK_TAG_JOIN_ORDER;
125 : : else if (tag == PGPA_TAG_INDEX_SCAN ||
126 : : tag == PGPA_TAG_INDEX_ONLY_SCAN)
127 : : return TOK_TAG_INDEX;
128 : : else if (tag == PGPA_TAG_BITMAP_HEAP_SCAN)
129 : : return TOK_TAG_BITMAP;
130 : : else if (tag == PGPA_TAG_SEQ_SCAN ||
131 : : tag == PGPA_TAG_TID_SCAN ||
132 : : tag == PGPA_TAG_NO_GATHER)
133 : : return TOK_TAG_SIMPLE;
134 : : else
135 : : return TOK_TAG_GENERIC;
136 : : }
137 : :
138 : : {decinteger} {
139 : : char *endptr;
140 : :
141 : : errno = 0;
142 : : yylval->integer = strtoint(yytext, &endptr, 10);
143 : : if (*endptr != '\0' || errno == ERANGE)
144 : : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
145 : : "integer out of range");
146 : : return TOK_INTEGER;
147 : : }
148 : :
149 : : {xcstart} {
150 : : BEGIN(xc);
151 : : }
152 : :
153 : : {xdstart} {
154 : : BEGIN(xd);
155 : : resetStringInfo(&yyextra->litbuf);
156 : : }
157 : :
158 : : "||" { return TOK_OR; }
159 : :
160 : : "&&" { return TOK_AND; }
161 : :
162 : : . { return yytext[0]; }
163 : :
164 : : <xc>{xcstop} {
165 : : BEGIN(INITIAL);
166 : : }
167 : :
168 : : <xc>{xcinside} {
169 : : /* discard multiple characters without slash or asterisk */
170 : : }
171 : :
172 : : <xc>. {
173 : : /*
174 : : * Discard any single character. flex prefers longer
175 : : * matches, so this rule will never be picked when we could
176 : : * have matched xcstop.
177 : : *
178 : : * NB: At present, we don't bother to support nested
179 : : * C-style comments here, but this logic could be extended
180 : : * if that restriction poses a problem.
181 : : */
182 : : }
183 : :
184 : : <xc><<EOF>> {
185 : : BEGIN(INITIAL);
186 : : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
187 : : "unterminated comment");
188 : : }
189 : :
190 : : <xd>{xdstop} {
191 : : BEGIN(INITIAL);
192 : : if (yyextra->litbuf.len == 0)
193 : : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
194 : : "zero-length delimited identifier");
195 : : yylval->str = pstrdup(yyextra->litbuf.data);
196 : : return TOK_IDENT;
197 : : }
198 : :
199 : : <xd>{xddouble} {
200 : : appendStringInfoChar(&yyextra->litbuf, '"');
201 : : }
202 : :
203 : : <xd>{xdinside} {
204 : : appendBinaryStringInfo(&yyextra->litbuf, yytext, yyleng);
205 : : }
206 : :
207 : : <xd><<EOF>> {
208 : : BEGIN(INITIAL);
209 : : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
210 : : "unterminated quoted identifier");
211 : : }
212 : :
213 : : %%
214 : :
215 : : /* LCOV_EXCL_STOP */
216 : :
217 : : /*
218 : : * Handler for errors while scanning or parsing advice.
219 : : *
220 : : * bison passes the error message to us via 'message', and the context is
221 : : * available via the 'yytext' macro. We assemble those values into a final
222 : : * error text and then arrange to pass it back to the caller of pgpa_yyparse()
223 : : * by storing it into *parse_error_msg_p.
224 : : */
225 : : void
226 : 18 : pgpa_yyerror(List **result, char **parse_error_msg_p, yyscan_t yyscanner,
227 : : const char *message)
228 : : {
229 : 18 : struct yyguts_t *yyg = (struct yyguts_t *) yyscanner; /* needed for yytext
230 : : * macro */
231 : :
232 : :
233 : : /* report only the first error in a parse operation */
234 [ + + ]: 18 : if (*parse_error_msg_p)
235 : 1 : return;
236 : :
237 [ + + ]: 17 : if (yytext[0])
238 : 11 : *parse_error_msg_p = psprintf("%s at or near \"%s\"", message, yytext);
239 : : else
240 : 6 : *parse_error_msg_p = psprintf("%s at end of input", message);
241 [ - + ]: 18 : }
242 : :
243 : : /*
244 : : * Initialize the advice scanner.
245 : : *
246 : : * This should be called before parsing begins.
247 : : */
248 : : void
249 : 43647 : pgpa_scanner_init(const char *str, yyscan_t *yyscannerp)
250 : : {
251 : 43647 : yyscan_t yyscanner;
252 : 43647 : pgpa_yy_extra_type *yyext = palloc0_object(pgpa_yy_extra_type);
253 : :
254 [ + - ]: 43647 : if (yylex_init(yyscannerp) != 0)
255 [ # # # # ]: 0 : elog(ERROR, "yylex_init() failed: %m");
256 : :
257 : 43647 : yyscanner = *yyscannerp;
258 : :
259 : 43647 : initStringInfo(&yyext->litbuf);
260 : 43647 : pgpa_yyset_extra(yyext, yyscanner);
261 : :
262 : 43647 : yy_scan_string(str, yyscanner);
263 : 43647 : }
264 : :
265 : :
266 : : /*
267 : : * Shut down the advice scanner.
268 : : *
269 : : * This should be called after parsing is complete.
270 : : */
271 : : void
272 : 43647 : pgpa_scanner_finish(yyscan_t yyscanner)
273 : : {
274 : 43647 : yylex_destroy(yyscanner);
275 : 43647 : }
276 : :
277 : : /*
278 : : * Interface functions to make flex use palloc() instead of malloc().
279 : : * It'd be better to make these static, but flex insists otherwise.
280 : : */
281 : :
282 : : void *
283 : 174588 : yyalloc(yy_size_t size, yyscan_t yyscanner)
284 : : {
285 : 174588 : return palloc(size);
286 : : }
287 : :
288 : : void *
289 : 0 : yyrealloc(void *ptr, yy_size_t size, yyscan_t yyscanner)
290 : : {
291 [ # # ]: 0 : if (ptr)
292 : 0 : return repalloc(ptr, size);
293 : : else
294 : 0 : return palloc(size);
295 : 0 : }
296 : :
297 : : void
298 : 218235 : yyfree(void *ptr, yyscan_t yyscanner)
299 : : {
300 [ + + ]: 218235 : if (ptr)
301 : 174588 : pfree(ptr);
302 : 218235 : }
|