LCOV - code coverage report
Current view: top level - src/bin/pgbench - exprscan.l (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 95.2 % 146 139
Test Date: 2026-02-17 17:20:33 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit

            Line data    Source code
       1              : %top{
       2              : /*-------------------------------------------------------------------------
       3              :  *
       4              :  * exprscan.l
       5              :  *    lexical scanner for pgbench backslash commands
       6              :  *
       7              :  * This lexer supports two operating modes:
       8              :  *
       9              :  * In INITIAL state, just parse off whitespace-separated words (this mode
      10              :  * is basically equivalent to strtok(), which is what we used to use).
      11              :  *
      12              :  * In EXPR state, lex for the simple expression syntax of exprparse.y.
      13              :  *
      14              :  * In either mode, stop upon hitting newline or end of string.
      15              :  *
      16              :  * Note that this lexer operates within the framework created by psqlscan.l,
      17              :  *
      18              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
      19              :  * Portions Copyright (c) 1994, Regents of the University of California
      20              :  *
      21              :  * src/bin/pgbench/exprscan.l
      22              :  *
      23              :  *-------------------------------------------------------------------------
      24              :  */
      25              : #include "postgres_fe.h"
      26              : 
      27              : /*
      28              :  * NB: include exprparse.h only AFTER including pgbench.h, because pgbench.h
      29              :  * contains definitions needed for YYSTYPE. Likewise, pgbench.h must come after
      30              :  * psqlscan_int.h for yyscan_t.
      31              :  */
      32              : #include "fe_utils/psqlscan_int.h"
      33              : #include "pgbench.h"
      34              : #include "exprparse.h"
      35              : }
      36              : 
      37              : %{
      38              : /* context information for reporting errors in expressions */
      39              : static const char *expr_source = NULL;
      40              : static int  expr_lineno = 0;
      41              : static int  expr_start_offset = 0;
      42              : static const char *expr_command = NULL;
      43              : 
      44              : /* indicates whether last yylex() call read a newline */
      45              : static bool last_was_newline = false;
      46              : 
      47              : /* LCOV_EXCL_START */
      48              : 
      49              : %}
      50              : 
      51              : /* Except for the prefix, these options should match psqlscan.l */
      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 warn
      61              : %option prefix="expr_yy"
      62              : 
      63              : /* Character classes */
      64              : alpha           [a-zA-Z\200-\377_]
      65              : digit           [0-9]
      66              : alnum           [A-Za-z\200-\377_0-9]
      67              : /* {space} + {nonspace} + {newline} should cover all characters */
      68              : space           [ \t\r\f\v]
      69              : nonspace        [^ \t\r\f\v\n]
      70              : newline         [\n]
      71              : 
      72              : /* Line continuation marker */
      73              : continuation    \\\r?{newline}
      74              : 
      75              : /* case insensitive keywords */
      76              : and             [Aa][Nn][Dd]
      77              : or              [Oo][Rr]
      78              : not             [Nn][Oo][Tt]
      79              : case            [Cc][Aa][Ss][Ee]
      80              : when            [Ww][Hh][Ee][Nn]
      81              : then            [Tt][Hh][Ee][Nn]
      82              : else            [Ee][Ll][Ss][Ee]
      83              : end             [Ee][Nn][Dd]
      84              : true            [Tt][Rr][Uu][Ee]
      85              : false           [Ff][Aa][Ll][Ss][Ee]
      86              : null            [Nn][Uu][Ll][Ll]
      87              : is              [Ii][Ss]
      88              : isnull          [Ii][Ss][Nn][Uu][Ll][Ll]
      89              : notnull         [Nn][Oo][Tt][Nn][Uu][Ll][Ll]
      90              : 
      91              : /* Exclusive states */
      92              : %x EXPR
      93              : 
      94              : %%
      95              : 
      96              : %{
      97              :         /* Declare some local variables inside yylex(), for convenience */
      98              :         PsqlScanState cur_state = yyextra;
      99         5107 : 
     100              :         /*
     101              :          * Force flex into the state indicated by start_state.  This has a
     102              :          * couple of purposes: it lets some of the functions below set a new
     103              :          * starting state without ugly direct access to flex variables, and it
     104              :          * allows us to transition from one flex lexer to another so that we
     105              :          * can lex different parts of the source string using separate lexers.
     106              :          */
     107              :         BEGIN(cur_state->start_state);
     108         5107 : 
     109              :         /* Reset was-newline flag */
     110              :         last_was_newline = false;
     111         5107 : %}
     112              : 
     113              :     /* INITIAL state */
     114              : 
     115              : {nonspace}+     {
     116              :                     /* Found a word, emit and return it */
     117              :                     psqlscan_emit(cur_state, yytext, yyleng);
     118         1185 :                     return 1;
     119         1185 :                 }
     120              : 
     121              :     /*
     122              :      * We need this rule to avoid returning "word\" instead of recognizing
     123              :      * a continuation marker just after a word:
     124              :      */
     125              : {nonspace}+{continuation}   {
     126            1 :                     /* Found "word\\\r?\n", emit and return just "word" */
     127              :                     int     wordlen = yyleng - 2;
     128            1 :                     if (yytext[wordlen] == '\r')
     129            1 :                         wordlen--;
     130            0 :                     Assert(yytext[wordlen] == '\\');
     131              :                     psqlscan_emit(cur_state, yytext, wordlen);
     132            1 :                     return 1;
     133            1 :                 }
     134              : 
     135              : {space}+        { /* ignore */ }
     136          655 : 
     137          655 : {continuation}  { /* ignore */ }
     138            1 : 
     139            1 : {newline}       {
     140          108 :                     /* report end of command */
     141              :                     last_was_newline = true;
     142          108 :                     return 0;
     143          108 :                 }
     144              : 
     145              :     /* EXPR state */
     146              : 
     147              : <EXPR>{
     148           53 : 
     149              : "+"               { return '+'; }
     150           53 : "-"               { return '-'; }
     151           65 : "*"               { return '*'; }
     152           65 : "/"               { return '/'; }
     153          204 : "%"               { return '%'; } /* C version, also in Pg SQL */
     154           13 : "="               { return '='; }
     155            2 : "<>"            { return NE_OP; }
     156           32 : "!="          { return NE_OP; } /* C version, also in Pg SQL */
     157            5 : "<="           { return LE_OP; }
     158            3 : ">="           { return GE_OP; }
     159            4 : "<<"            { return LS_OP; }
     160            3 : ">>"            { return RS_OP; }
     161            7 : "<"                { return '<'; }
     162            1 : ">"                { return '>'; }
     163           10 : "|"               { return '|'; }
     164            6 : "&"               { return '&'; }
     165            2 : "#"               { return '#'; }
     166            1 : "~"               { return '~'; }
     167            1 : 
     168            2 : "("               { return '('; }
     169          451 : ")"               { return ')'; }
     170          453 : ","               { return ','; }
     171          453 : 
     172          343 : {and}           { return AND_OP; }
     173           44 : {or}            { return OR_OP; }
     174            5 : {not}           { return NOT_OP; }
     175            5 : {is}            { return IS_OP; }
     176           15 : {isnull}        { return ISNULL_OP; }
     177            8 : {notnull}       { return NOTNULL_OP; }
     178            1 : 
     179            1 : {case}          { return CASE_KW; }
     180           15 : {when}          { return WHEN_KW; }
     181           16 : {then}          { return THEN_KW; }
     182           16 : {else}          { return ELSE_KW; }
     183           17 : {end}           { return END_KW; }
     184           11 : 
     185           15 : :{alnum}+       {
     186          272 :                     yylval->str = pg_strdup(yytext + 1);
     187          272 :                     return VARIABLE;
     188          272 :                 }
     189              : 
     190              : {null}          { return NULL_CONST; }
     191           12 : {true}          {
     192              :                     yylval->bval = true;
     193           21 :                     return BOOLEAN_CONST;
     194           21 :                 }
     195              : {false}         {
     196              :                     yylval->bval = false;
     197           13 :                     return BOOLEAN_CONST;
     198           13 :                 }
     199              : "9223372036854775808" {
     200              :                     /*
     201            1 :                      * Special handling for PG_INT64_MIN, which can't
     202              :                      * accurately be represented here, as the minus sign is
     203              :                      * lexed separately and INT64_MIN can't be represented as
     204              :                      * a positive integer.
     205              :                      */
     206              :                     return MAXINT_PLUS_ONE_CONST;
     207            1 :                 }
     208              : {digit}+        {
     209              :                     if (!strtoint64(yytext, true, &yylval->ival))
     210          797 :                         expr_yyerror_more(yyscanner, "bigint constant overflow",
     211            1 :                                           strdup(yytext));
     212            1 :                     return INTEGER_CONST;
     213          796 :                 }
     214              : {digit}+(\.{digit}*)?([eE][-+]?{digit}+)?   {
     215              :                     if (!strtodouble(yytext, true, &yylval->dval))
     216           61 :                         expr_yyerror_more(yyscanner, "double constant overflow",
     217            1 :                                           strdup(yytext));
     218            1 :                     return DOUBLE_CONST;
     219           60 :                 }
     220              : \.{digit}+([eE][-+]?{digit}+)?  {
     221              :                     if (!strtodouble(yytext, true, &yylval->dval))
     222            2 :                         expr_yyerror_more(yyscanner, "double constant overflow",
     223            1 :                                           strdup(yytext));
     224            1 :                     return DOUBLE_CONST;
     225            1 :                 }
     226              : {alpha}{alnum}* {
     227              :                     yylval->str = pg_strdup(yytext);
     228          399 :                     return FUNCTION;
     229          399 :                 }
     230              : 
     231              : {space}+        { /* ignore */ }
     232         1656 : 
     233         1656 : {continuation}  { /* ignore */ }
     234           17 : 
     235           17 : {newline}       {
     236          368 :                     /* report end of command */
     237              :                     last_was_newline = true;
     238          368 :                     return 0;
     239          368 :                 }
     240              : 
     241              : .               {
     242            1 :                     /*
     243              :                      * must strdup yytext so that expr_yyerror_more doesn't
     244              :                      * change it while finding end of line
     245              :                      */
     246              :                     expr_yyerror_more(yyscanner, "unexpected character",
     247            1 :                                       pg_strdup(yytext));
     248            1 :                     /* NOTREACHED, syntax_error calls exit() */
     249              :                     return 0;
     250              :                 }
     251              : 
     252              : }
     253              : 
     254           57 : <<EOF>>         {
     255              :                     if (cur_state->buffer_stack == NULL)
     256           57 :                         return 0;           /* end of input reached */
     257           57 : 
     258              :                     /*
     259              :                      * We were expanding a variable, so pop the inclusion
     260              :                      * stack and keep lexing
     261              :                      */
     262              :                     psqlscan_pop_buffer_stack(cur_state);
     263            0 :                     psqlscan_select_top_buffer(cur_state);
     264            0 :                 }
     265              : 
     266            0 : %%
     267            0 : 
     268              : /* LCOV_EXCL_STOP */
     269              : 
     270              : void
     271              : expr_yyerror_more(yyscan_t yyscanner, const char *message, const char *more)
     272           19 : {
     273              :     PsqlScanState state = yyget_extra(yyscanner);
     274           19 :     int         lineno;
     275              :     int         error_detection_offset;
     276              :     YYSTYPE     lval;
     277              :     char       *full_line;
     278              : 
     279              :     psql_scan_get_location(state, &lineno, &error_detection_offset);
     280           19 :     error_detection_offset--;
     281           19 : 
     282              :     /*
     283              :      * While parsing an expression, we may not have collected the whole line
     284              :      * yet from the input source.  Lex till EOL so we can report whole line.
     285              :      * (If we're at EOF, it's okay to call yylex() an extra time.)
     286              :      */
     287              :     if (!last_was_newline)
     288           19 :     {
     289              :         while (yylex(&lval, yyscanner))
     290           25 :              /* skip */ ;
     291              :     }
     292              : 
     293              :     /* Extract the line, trimming trailing newline if any */
     294              :     full_line = expr_scanner_get_substring(state,
     295           19 :                                            expr_start_offset,
     296              :                                            true);
     297              : 
     298              :     syntax_error(expr_source, expr_lineno, full_line, expr_command,
     299           19 :                  message, more, error_detection_offset - expr_start_offset);
     300              : }
     301              : 
     302              : /*
     303              :  * (The first argument is enforced by Bison to match the first argument of
     304              :  * yyparse(), but it is not used here.)
     305              :  */
     306              : void
     307              : expr_yyerror(PgBenchExpr **expr_parse_result_p, yyscan_t yyscanner, const char *message)
     308            6 : {
     309              :     expr_yyerror_more(yyscanner, message, NULL);
     310            6 : }
     311              : 
     312              : /*
     313              :  * Collect a space-separated word from a backslash command and return it
     314              :  * in word_buf, along with its starting string offset in *offset.
     315              :  * Returns true if successful, false if at end of command.
     316              :  */
     317              : bool
     318              : expr_lex_one_word(PsqlScanState state, PQExpBuffer word_buf, int *offset)
     319         1318 : {
     320              :     int         lexresult;
     321              :     YYSTYPE     lval;
     322              : 
     323              :     /* Must be scanning already */
     324              :     Assert(state->scanbufhandle != NULL);
     325              : 
     326              :     /* Set current output target */
     327              :     state->output_buf = word_buf;
     328         1318 :     resetPQExpBuffer(word_buf);
     329         1318 : 
     330              :     /* Set input source */
     331              :     if (state->buffer_stack != NULL)
     332         1318 :         yy_switch_to_buffer(state->buffer_stack->buf, state->scanner);
     333            0 :     else
     334              :         yy_switch_to_buffer(state->scanbufhandle, state->scanner);
     335         1318 : 
     336              :     /* Set start state */
     337              :     state->start_state = INITIAL;
     338         1318 : 
     339              :     /* And lex. */
     340              :     lexresult = yylex(&lval, state->scanner);
     341         1318 : 
     342              :     /* Save start offset of word, if any. */
     343              :     if (lexresult)
     344         1318 :     {
     345              :         int         lineno;
     346              :         int         end_offset;
     347              : 
     348              :         psql_scan_get_location(state, &lineno, &end_offset);
     349         1186 :         *offset = end_offset - word_buf->len;
     350         1186 :     }
     351              :     else
     352              :         *offset = -1;
     353          132 : 
     354              :     /*
     355              :      * In case the caller returns to using the regular SQL lexer, reselect the
     356              :      * appropriate initial state.
     357              :      */
     358              :     psql_scan_reselect_sql_lexer(state);
     359         1318 : 
     360              :     return (bool) lexresult;
     361         1318 : }
     362              : 
     363              : /*
     364              :  * Prepare to lex an expression via expr_yyparse().
     365              :  *
     366              :  * Returns the yyscan_t that is to be passed to expr_yyparse().
     367              :  * (This is just state->scanner, but callers don't need to know that.)
     368              :  */
     369              : yyscan_t
     370              : expr_scanner_init(PsqlScanState state,
     371          399 :                   const char *source, int lineno, int start_offset,
     372              :                   const char *command)
     373              : {
     374              :     /* Save error context info */
     375              :     expr_source = source;
     376          399 :     expr_lineno = lineno;
     377          399 :     expr_start_offset = start_offset;
     378          399 :     expr_command = command;
     379          399 : 
     380              :     /* Must be scanning already */
     381              :     Assert(state->scanbufhandle != NULL);
     382              : 
     383              :     /* Set current output target */
     384              :     state->output_buf = NULL;
     385          399 : 
     386              :     /* Set input source */
     387              :     if (state->buffer_stack != NULL)
     388          399 :         yy_switch_to_buffer(state->buffer_stack->buf, state->scanner);
     389            0 :     else
     390              :         yy_switch_to_buffer(state->scanbufhandle, state->scanner);
     391          399 : 
     392              :     /* Set start state */
     393              :     state->start_state = EXPR;
     394          399 : 
     395              :     return state->scanner;
     396          399 : }
     397              : 
     398              : /*
     399              :  * Finish lexing an expression.
     400              :  */
     401              : void
     402              : expr_scanner_finish(yyscan_t yyscanner)
     403          380 : {
     404              :     PsqlScanState state = yyget_extra(yyscanner);
     405          380 : 
     406              :     /*
     407              :      * Reselect appropriate initial state for SQL lexer.
     408              :      */
     409              :     psql_scan_reselect_sql_lexer(state);
     410          380 : }
     411          380 : 
     412              : /*
     413              :  * Get a malloc'd copy of the lexer input string from start_offset
     414              :  * to end of current lexer token.  If chomp is true, drop any trailing
     415              :  * newline(s).
     416              :  *
     417              :  * We rely on the knowledge that flex modifies the scan buffer by storing
     418              :  * a NUL at the end of the current token (yytext).  Note that this might
     419              :  * not work quite right if we were parsing a sub-buffer, but since pgbench
     420              :  * never invokes that functionality, it doesn't matter.  Also, this will
     421              :  * give the wrong answer (the whole remainder of the input) if called
     422              :  * before any yylex() call has been done.
     423              :  */
     424              : char *
     425              : expr_scanner_get_substring(PsqlScanState state,
     426          530 :                            int start_offset,
     427              :                            bool chomp)
     428              : {
     429              :     char       *result;
     430              :     const char *scanptr = state->scanbuf + start_offset;
     431          530 :     size_t      slen = strlen(scanptr);
     432          530 : 
     433              :     if (chomp)
     434          530 :     {
     435              :         while (slen > 0 &&
     436         1006 :                (scanptr[slen - 1] == '\n' || scanptr[slen - 1] == '\r'))
     437         1006 :             slen--;
     438          476 :     }
     439              : 
     440              :     result = (char *) pg_malloc(slen + 1);
     441          530 :     memcpy(result, scanptr, slen);
     442          530 :     result[slen] = '\0';
     443          530 : 
     444              :     return result;
     445          530 : }
     446              : /* END: function "expr_scanner_get_substring" */
        

Generated by: LCOV version 2.0-1