Age Owner Branch data TLA 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
3 rhaas@postgresql.org 46 :UNC 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 */ }
3 rhaas@postgresql.org 102 :GNC 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)
3 rhaas@postgresql.org 143 :UNC 0 : pgpa_yyerror(result, parse_error_msg_p, yyscanner,
144 : : "integer out of range");
3 rhaas@postgresql.org 145 :GNC 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 : :
3 rhaas@postgresql.org 194 :UNC 0 : <xd>{xddouble} {
195 : 0 : appendStringInfoChar(&yyextra->litbuf, '"');
196 : : }
197 : 0 :
3 rhaas@postgresql.org 198 :GNC 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 :
3 rhaas@postgresql.org 208 :UNC 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
3 rhaas@postgresql.org 221 :GNC 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)
3 rhaas@postgresql.org 250 [ # # ]:UNC 0 : elog(ERROR, "yylex_init() failed: %m");
251 : :
3 rhaas@postgresql.org 252 :GNC 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 *
3 rhaas@postgresql.org 284 :UNC 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
3 rhaas@postgresql.org 293 :GNC 1215 : yyfree(void *ptr, yyscan_t yyscanner)
294 : : {
295 [ + + ]: 1215 : if (ptr)
296 : 972 : pfree(ptr);
297 : 1215 : }
|