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