LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_scanner.l (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 86.7 % 90 78
Test Date: 2026-06-30 20:16:43 Functions: 71.4 % 7 5
Legend: Lines:     hit not hit

            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       127944 : 
     103       323952 : {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       323952 :                     str = downcase_identifier(yytext, yyleng, false, false);
     113       323952 :                     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       323952 :                     tag = pgpa_parse_advice_tag(str, &fail);
     121       323952 :                     if (fail)
     122       229191 :                         return TOK_IDENT;
     123        94761 :                     else if (tag == PGPA_TAG_JOIN_ORDER)
     124        10933 :                         return TOK_TAG_JOIN_ORDER;
     125        83828 :                     else if (tag == PGPA_TAG_INDEX_SCAN ||
     126              :                              tag == PGPA_TAG_INDEX_ONLY_SCAN)
     127         8714 :                         return TOK_TAG_INDEX;
     128        75114 :                     else if (tag == PGPA_TAG_SEQ_SCAN ||
     129        58315 :                              tag == PGPA_TAG_TID_SCAN ||
     130        55982 :                              tag == PGPA_TAG_BITMAP_HEAP_SCAN ||
     131        12568 :                              tag == PGPA_TAG_NO_GATHER ||
     132              :                              tag == PGPA_TAG_DO_NOT_SCAN)
     133        62727 :                         return TOK_TAG_SIMPLE;
     134              :                     else
     135        12387 :                         return TOK_TAG_GENERIC;
     136              :                 }
     137              : 
     138         3392 : {decinteger}    {
     139              :                     char   *endptr;
     140              : 
     141         3392 :                     errno = 0;
     142         3392 :                     yylval->integer = strtoint(yytext, &endptr, 10);
     143         3392 :                     if (*endptr != '\0' || errno == ERANGE)
     144            0 :                         pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     145              :                                      "integer out of range");
     146         3392 :                     return TOK_INTEGER;
     147              :                 }
     148              : 
     149           17 : {xcstart}       {
     150           17 :                     BEGIN(xc);
     151              :                 }
     152           17 : 
     153        20532 : {xdstart}       {
     154        20532 :                     BEGIN(xd);
     155        20532 :                     resetStringInfo(&yyextra->litbuf);
     156              :                 }
     157        20532 : 
     158       273659 : .               { 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        20531 : <xd>{xdstop}  {
     187        20531 :                     BEGIN(INITIAL);
     188        20531 :                     if (yyextra->litbuf.len == 0)
     189            1 :                         pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     190              :                                      "zero-length delimited identifier");
     191        20531 :                     yylval->str = pstrdup(yyextra->litbuf.data);
     192        20531 :                     return TOK_IDENT;
     193              :                 }
     194              : 
     195            0 : <xd>{xddouble}    {
     196            0 :                     appendStringInfoChar(&yyextra->litbuf, '"');
     197              :                 }
     198            0 : 
     199        20530 : <xd>{xdinside}    {
     200        20530 :                     appendBinaryStringInfo(&yyextra->litbuf, yytext, yyleng);
     201              :                 }
     202        20530 : 
     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 : 
     209            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
     222           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        43840 : pgpa_scanner_init(const char *str, yyscan_t *yyscannerp)
     246              : {
     247              :     yyscan_t    yyscanner;
     248        43840 :     pgpa_yy_extra_type  *yyext = palloc0_object(pgpa_yy_extra_type);
     249              : 
     250        43840 :     if (yylex_init(yyscannerp) != 0)
     251            0 :         elog(ERROR, "yylex_init() failed: %m");
     252              : 
     253        43840 :     yyscanner = *yyscannerp;
     254              : 
     255        43840 :     initStringInfo(&yyext->litbuf);
     256        43840 :     pgpa_yyset_extra(yyext, yyscanner);
     257              : 
     258        43840 :     yy_scan_string(str, yyscanner);
     259        43840 : }
     260              : 
     261              : 
     262              : /*
     263              :  * Shut down the advice scanner.
     264              :  *
     265              :  * This should be called after parsing is complete.
     266              :  */
     267              : void
     268        43840 : pgpa_scanner_finish(yyscan_t yyscanner)
     269              : {
     270        43840 :     yylex_destroy(yyscanner);
     271        43840 : }
     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       175360 : yyalloc(yy_size_t size, yyscan_t yyscanner)
     280              : {
     281       175360 :     return palloc(size);
     282              : }
     283              : 
     284              : void *
     285            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
     294       219200 : yyfree(void *ptr, yyscan_t yyscanner)
     295              : {
     296       219200 :     if (ptr)
     297       175360 :         pfree(ptr);
     298       219200 : }
        

Generated by: LCOV version 2.0-1