LCOV - code coverage report
Current view: top level - src/backend/utils/adt - tsvector_parser.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 86.7 % 173 150
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 68.9 % 222 153

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * tsvector_parser.c
       4                 :             :  *        Parser for tsvector
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7                 :             :  *
       8                 :             :  *
       9                 :             :  * IDENTIFICATION
      10                 :             :  *        src/backend/utils/adt/tsvector_parser.c
      11                 :             :  *
      12                 :             :  *-------------------------------------------------------------------------
      13                 :             :  */
      14                 :             : 
      15                 :             : #include "postgres.h"
      16                 :             : 
      17                 :             : #include "tsearch/ts_locale.h"
      18                 :             : #include "tsearch/ts_utils.h"
      19                 :             : 
      20                 :             : 
      21                 :             : /*
      22                 :             :  * Private state of tsvector parser.  Note that tsquery also uses this code to
      23                 :             :  * parse its input, hence the boolean flags.  The oprisdelim and is_tsquery
      24                 :             :  * flags are both true or both false in current usage, but we keep them
      25                 :             :  * separate for clarity.
      26                 :             :  *
      27                 :             :  * If oprisdelim is set, the following characters are treated as delimiters
      28                 :             :  * (in addition to whitespace): ! | & ( )
      29                 :             :  *
      30                 :             :  * is_tsquery affects *only* the content of error messages.
      31                 :             :  *
      32                 :             :  * is_web can be true to further modify tsquery parsing.
      33                 :             :  *
      34                 :             :  * If escontext is an ErrorSaveContext node, then soft errors can be
      35                 :             :  * captured there rather than being thrown.
      36                 :             :  */
      37                 :             : struct TSVectorParseStateData
      38                 :             : {
      39                 :             :         char       *prsbuf;                     /* next input character */
      40                 :             :         char       *bufstart;           /* whole string (used only for errors) */
      41                 :             :         char       *word;                       /* buffer to hold the current word */
      42                 :             :         int                     len;                    /* size in bytes allocated for 'word' */
      43                 :             :         int                     eml;                    /* max bytes per character */
      44                 :             :         bool            oprisdelim;             /* treat ! | * ( ) as delimiters? */
      45                 :             :         bool            is_tsquery;             /* say "tsquery" not "tsvector" in errors? */
      46                 :             :         bool            is_web;                 /* we're in websearch_to_tsquery() */
      47                 :             :         Node       *escontext;          /* for soft error reporting */
      48                 :             : };
      49                 :             : 
      50                 :             : 
      51                 :             : /*
      52                 :             :  * Initializes a parser state object for the given input string.
      53                 :             :  * A bitmask of flags (see ts_utils.h) and an error context object
      54                 :             :  * can be provided as well.
      55                 :             :  */
      56                 :             : TSVectorParseState
      57                 :        1269 : init_tsvector_parser(char *input, int flags, Node *escontext)
      58                 :             : {
      59                 :        1269 :         TSVectorParseState state;
      60                 :             : 
      61                 :        1269 :         state = palloc_object(struct TSVectorParseStateData);
      62                 :        1269 :         state->prsbuf = input;
      63                 :        1269 :         state->bufstart = input;
      64                 :        1269 :         state->len = 32;
      65                 :        1269 :         state->word = (char *) palloc(state->len);
      66                 :        1269 :         state->eml = pg_database_encoding_max_length();
      67                 :        1269 :         state->oprisdelim = (flags & P_TSV_OPR_IS_DELIM) != 0;
      68                 :        1269 :         state->is_tsquery = (flags & P_TSV_IS_TSQUERY) != 0;
      69                 :        1269 :         state->is_web = (flags & P_TSV_IS_WEB) != 0;
      70                 :        1269 :         state->escontext = escontext;
      71                 :             : 
      72                 :        2538 :         return state;
      73                 :        1269 : }
      74                 :             : 
      75                 :             : /*
      76                 :             :  * Reinitializes parser to parse 'input', instead of previous input.
      77                 :             :  *
      78                 :             :  * Note that bufstart (the string reported in errors) is not changed.
      79                 :             :  */
      80                 :             : void
      81                 :        1352 : reset_tsvector_parser(TSVectorParseState state, char *input)
      82                 :             : {
      83                 :        1352 :         state->prsbuf = input;
      84                 :        1352 : }
      85                 :             : 
      86                 :             : /*
      87                 :             :  * Shuts down a tsvector parser.
      88                 :             :  */
      89                 :             : void
      90                 :        1268 : close_tsvector_parser(TSVectorParseState state)
      91                 :             : {
      92                 :        1268 :         pfree(state->word);
      93                 :        1268 :         pfree(state);
      94                 :        1268 : }
      95                 :             : 
      96                 :             : /* increase the size of 'word' if needed to hold one more character */
      97                 :             : #define RESIZEPRSBUF \
      98                 :             : do { \
      99                 :             :         int clen = curpos - state->word; \
     100                 :             :         if ( clen + state->eml >= state->len ) \
     101                 :             :         { \
     102                 :             :                 state->len *= 2; \
     103                 :             :                 state->word = (char *) repalloc(state->word, state->len); \
     104                 :             :                 curpos = state->word + clen; \
     105                 :             :         } \
     106                 :             : } while (0)
     107                 :             : 
     108                 :             : /* Fills gettoken_tsvector's output parameters, and returns true */
     109                 :             : #define RETURN_TOKEN \
     110                 :             : do { \
     111                 :             :         if (pos_ptr != NULL) \
     112                 :             :         { \
     113                 :             :                 *pos_ptr = pos; \
     114                 :             :                 *poslen = npos; \
     115                 :             :         } \
     116                 :             :         else if (pos != NULL) \
     117                 :             :                 pfree(pos); \
     118                 :             :         \
     119                 :             :         if (strval != NULL) \
     120                 :             :                 *strval = state->word; \
     121                 :             :         if (lenval != NULL) \
     122                 :             :                 *lenval = curpos - state->word; \
     123                 :             :         if (endptr != NULL) \
     124                 :             :                 *endptr = state->prsbuf; \
     125                 :             :         return true; \
     126                 :             : } while(0)
     127                 :             : 
     128                 :             : 
     129                 :             : /* State codes used in gettoken_tsvector */
     130                 :             : #define WAITWORD                1
     131                 :             : #define WAITENDWORD             2
     132                 :             : #define WAITNEXTCHAR    3
     133                 :             : #define WAITENDCMPLX    4
     134                 :             : #define WAITPOSINFO             5
     135                 :             : #define INPOSINFO               6
     136                 :             : #define WAITPOSDELIM    7
     137                 :             : #define WAITCHARCMPLX   8
     138                 :             : 
     139                 :             : #define PRSSYNTAXERROR return prssyntaxerror(state)
     140                 :             : 
     141                 :             : static bool
     142                 :           3 : prssyntaxerror(TSVectorParseState state)
     143                 :             : {
     144   [ +  +  -  + ]:           3 :         errsave(state->escontext,
     145                 :             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     146                 :             :                          state->is_tsquery ?
     147                 :             :                          errmsg("syntax error in tsquery: \"%s\"", state->bufstart) :
     148                 :             :                          errmsg("syntax error in tsvector: \"%s\"", state->bufstart)));
     149                 :             :         /* In soft error situation, return false as convenience for caller */
     150                 :           3 :         return false;
     151                 :             : }
     152                 :             : 
     153                 :             : 
     154                 :             : /*
     155                 :             :  * Get next token from string being parsed. Returns true if successful,
     156                 :             :  * false if end of input string is reached or soft error.
     157                 :             :  *
     158                 :             :  * On success, these output parameters are filled in:
     159                 :             :  *
     160                 :             :  * *strval              pointer to token
     161                 :             :  * *lenval              length of *strval
     162                 :             :  * *pos_ptr             pointer to a palloc'd array of positions and weights
     163                 :             :  *                              associated with the token. If the caller is not interested
     164                 :             :  *                              in the information, NULL can be supplied. Otherwise
     165                 :             :  *                              the caller is responsible for pfreeing the array.
     166                 :             :  * *poslen              number of elements in *pos_ptr
     167                 :             :  * *endptr              scan resumption point
     168                 :             :  *
     169                 :             :  * Pass NULL for any unwanted output parameters.
     170                 :             :  *
     171                 :             :  * If state->escontext is an ErrorSaveContext, then caller must check
     172                 :             :  * SOFT_ERROR_OCCURRED() to determine whether a "false" result means
     173                 :             :  * error or normal end-of-string.
     174                 :             :  */
     175                 :             : bool
     176                 :       32112 : gettoken_tsvector(TSVectorParseState state,
     177                 :             :                                   char **strval, int *lenval,
     178                 :             :                                   WordEntryPos **pos_ptr, int *poslen,
     179                 :             :                                   char **endptr)
     180                 :             : {
     181                 :       32112 :         int                     oldstate = 0;
     182                 :       32112 :         char       *curpos = state->word;
     183                 :       32112 :         int                     statecode = WAITWORD;
     184                 :             : 
     185                 :             :         /*
     186                 :             :          * pos is for collecting the comma delimited list of positions followed by
     187                 :             :          * the actual token.
     188                 :             :          */
     189                 :       32112 :         WordEntryPos *pos = NULL;
     190                 :       32112 :         int                     npos = 0;               /* elements of pos used */
     191                 :       32112 :         int                     posalen = 0;    /* allocated size of pos */
     192                 :             : 
     193                 :      130833 :         while (1)
     194                 :             :         {
     195         [ +  + ]:      130833 :                 if (statecode == WAITWORD)
     196                 :             :                 {
     197         [ +  + ]:       61672 :                         if (*(state->prsbuf) == '\0')
     198                 :         628 :                                 return false;
     199   [ +  +  +  + ]:       61044 :                         else if (!state->is_web && t_iseq(state->prsbuf, '\''))
     200                 :          27 :                                 statecode = WAITENDCMPLX;
     201   [ +  +  +  + ]:       61017 :                         else if (!state->is_web && t_iseq(state->prsbuf, '\\'))
     202                 :             :                         {
     203                 :           1 :                                 statecode = WAITNEXTCHAR;
     204                 :           1 :                                 oldstate = WAITENDWORD;
     205                 :           1 :                         }
     206   [ +  +  +  -  :       61167 :                         else if ((state->oprisdelim && ISOPERATOR(state->prsbuf)) ||
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
     207         [ +  + ]:       61016 :                                          (state->is_web && t_iseq(state->prsbuf, '"')))
     208                 :           0 :                                 PRSSYNTAXERROR;
     209         [ +  + ]:       61016 :                         else if (!isspace((unsigned char) *state->prsbuf))
     210                 :             :                         {
     211                 :       31456 :                                 COPYCHAR(curpos, state->prsbuf);
     212                 :       31456 :                                 curpos += pg_mblen(state->prsbuf);
     213                 :       31456 :                                 statecode = WAITENDWORD;
     214                 :       31456 :                         }
     215                 :       61044 :                 }
     216         [ +  + ]:       69161 :                 else if (statecode == WAITNEXTCHAR)
     217                 :             :                 {
     218         [ +  - ]:          27 :                         if (*(state->prsbuf) == '\0')
     219         [ #  # ]:           0 :                                 ereturn(state->escontext, false,
     220                 :             :                                                 (errcode(ERRCODE_SYNTAX_ERROR),
     221                 :             :                                                  errmsg("there is no escaped character: \"%s\"",
     222                 :             :                                                                 state->bufstart)));
     223                 :             :                         else
     224                 :             :                         {
     225         [ +  - ]:          27 :                                 RESIZEPRSBUF;
     226                 :          27 :                                 COPYCHAR(curpos, state->prsbuf);
     227                 :          27 :                                 curpos += pg_mblen(state->prsbuf);
     228         [ -  + ]:          27 :                                 Assert(oldstate != 0);
     229                 :          27 :                                 statecode = oldstate;
     230                 :             :                         }
     231                 :          27 :                 }
     232         [ +  + ]:       69134 :                 else if (statecode == WAITENDWORD)
     233                 :             :                 {
     234   [ +  +  +  + ]:       63863 :                         if (!state->is_web && t_iseq(state->prsbuf, '\\'))
     235                 :             :                         {
     236                 :          12 :                                 statecode = WAITNEXTCHAR;
     237                 :          12 :                                 oldstate = WAITENDWORD;
     238                 :          12 :                         }
     239   [ +  +  +  + ]:       63851 :                         else if (isspace((unsigned char) *state->prsbuf) || *(state->prsbuf) == '\0' ||
     240   [ +  +  +  -  :       34820 :                                          (state->oprisdelim && ISOPERATOR(state->prsbuf)) ||
          +  +  +  +  +  
          +  +  -  +  +  
                   +  + ]
     241         [ +  + ]:       34166 :                                          (state->is_web && t_iseq(state->prsbuf, '"')))
     242                 :             :                         {
     243         [ +  - ]:       29688 :                                 RESIZEPRSBUF;
     244         [ +  - ]:       29688 :                                 if (curpos == state->word)
     245                 :           0 :                                         PRSSYNTAXERROR;
     246                 :       29688 :                                 *(curpos) = '\0';
     247   [ +  +  -  +  :       30909 :                                 RETURN_TOKEN;
          +  -  +  -  +  
                      + ]
     248                 :           0 :                         }
     249         [ +  + ]:       34163 :                         else if (t_iseq(state->prsbuf, ':'))
     250                 :             :                         {
     251         [ -  + ]:        1769 :                                 if (curpos == state->word)
     252                 :           0 :                                         PRSSYNTAXERROR;
     253                 :        1769 :                                 *(curpos) = '\0';
     254         [ +  + ]:        1769 :                                 if (state->oprisdelim)
     255   [ -  +  +  -  :         232 :                                         RETURN_TOKEN;
          +  -  +  -  +  
                      - ]
     256                 :             :                                 else
     257                 :        1653 :                                         statecode = INPOSINFO;
     258                 :        1653 :                         }
     259                 :             :                         else
     260                 :             :                         {
     261         [ +  - ]:       32394 :                                 RESIZEPRSBUF;
     262                 :       32394 :                                 COPYCHAR(curpos, state->prsbuf);
     263                 :       32394 :                                 curpos += pg_mblen(state->prsbuf);
     264                 :             :                         }
     265                 :       34059 :                 }
     266         [ +  + ]:        5271 :                 else if (statecode == WAITENDCMPLX)
     267                 :             :                 {
     268   [ +  -  +  + ]:         154 :                         if (!state->is_web && t_iseq(state->prsbuf, '\''))
     269                 :             :                         {
     270                 :          27 :                                 statecode = WAITCHARCMPLX;
     271                 :          27 :                         }
     272   [ +  -  +  + ]:         127 :                         else if (!state->is_web && t_iseq(state->prsbuf, '\\'))
     273                 :             :                         {
     274                 :          14 :                                 statecode = WAITNEXTCHAR;
     275                 :          14 :                                 oldstate = WAITENDCMPLX;
     276                 :          14 :                         }
     277         [ +  - ]:         113 :                         else if (*(state->prsbuf) == '\0')
     278                 :           0 :                                 PRSSYNTAXERROR;
     279                 :             :                         else
     280                 :             :                         {
     281         [ +  - ]:         113 :                                 RESIZEPRSBUF;
     282                 :         113 :                                 COPYCHAR(curpos, state->prsbuf);
     283                 :         113 :                                 curpos += pg_mblen(state->prsbuf);
     284                 :             :                         }
     285                 :         154 :                 }
     286         [ +  + ]:        5117 :                 else if (statecode == WAITCHARCMPLX)
     287                 :             :                 {
     288   [ +  -  +  - ]:          27 :                         if (!state->is_web && t_iseq(state->prsbuf, '\''))
     289                 :             :                         {
     290         [ #  # ]:           0 :                                 RESIZEPRSBUF;
     291                 :           0 :                                 COPYCHAR(curpos, state->prsbuf);
     292                 :           0 :                                 curpos += pg_mblen(state->prsbuf);
     293                 :           0 :                                 statecode = WAITENDCMPLX;
     294                 :           0 :                         }
     295                 :             :                         else
     296                 :             :                         {
     297         [ +  - ]:          27 :                                 RESIZEPRSBUF;
     298                 :          27 :                                 *(curpos) = '\0';
     299         [ +  + ]:          27 :                                 if (curpos == state->word)
     300                 :           3 :                                         PRSSYNTAXERROR;
     301         [ +  + ]:          24 :                                 if (state->oprisdelim)
     302                 :             :                                 {
     303                 :             :                                         /* state->prsbuf+=pg_mblen(state->prsbuf); */
     304   [ +  -  -  +  :          22 :                                         RETURN_TOKEN;
          -  +  -  +  -  
                      + ]
     305                 :           0 :                                 }
     306                 :             :                                 else
     307                 :          13 :                                         statecode = WAITPOSINFO;
     308                 :          13 :                                 continue;               /* recheck current character */
     309                 :             :                         }
     310                 :           0 :                 }
     311         [ +  + ]:        5090 :                 else if (statecode == WAITPOSINFO)
     312                 :             :                 {
     313         [ -  + ]:          13 :                         if (t_iseq(state->prsbuf, ':'))
     314                 :           0 :                                 statecode = INPOSINFO;
     315                 :             :                         else
     316   [ +  -  #  #  :          13 :                                 RETURN_TOKEN;
          -  +  -  +  +  
                      - ]
     317                 :           0 :                 }
     318         [ +  + ]:        5077 :                 else if (statecode == INPOSINFO)
     319                 :             :                 {
     320         [ +  - ]:        1754 :                         if (isdigit((unsigned char) *state->prsbuf))
     321                 :             :                         {
     322         [ +  + ]:        1754 :                                 if (posalen == 0)
     323                 :             :                                 {
     324                 :        1653 :                                         posalen = 4;
     325                 :        1653 :                                         pos = palloc_array(WordEntryPos, posalen);
     326                 :        1653 :                                         npos = 0;
     327                 :        1653 :                                 }
     328         [ +  + ]:         101 :                                 else if (npos + 1 >= posalen)
     329                 :             :                                 {
     330                 :          19 :                                         posalen *= 2;
     331                 :          19 :                                         pos = repalloc_array(pos, WordEntryPos, posalen);
     332                 :          19 :                                 }
     333                 :        1754 :                                 npos++;
     334         [ -  + ]:        1754 :                                 WEP_SETPOS(pos[npos - 1], LIMITPOS(atoi(state->prsbuf)));
     335                 :             :                                 /* we cannot get here in tsquery, so no need for 2 errmsgs */
     336         [ +  - ]:        1754 :                                 if (WEP_GETPOS(pos[npos - 1]) == 0)
     337         [ #  # ]:           0 :                                         ereturn(state->escontext, false,
     338                 :             :                                                         (errcode(ERRCODE_SYNTAX_ERROR),
     339                 :             :                                                          errmsg("wrong position info in tsvector: \"%s\"",
     340                 :             :                                                                         state->bufstart)));
     341                 :        1754 :                                 WEP_SETWEIGHT(pos[npos - 1], 0);
     342                 :        1754 :                                 statecode = WAITPOSDELIM;
     343                 :        1754 :                         }
     344                 :             :                         else
     345                 :           0 :                                 PRSSYNTAXERROR;
     346                 :        1754 :                 }
     347         [ -  + ]:        3323 :                 else if (statecode == WAITPOSDELIM)
     348                 :             :                 {
     349         [ +  + ]:        3323 :                         if (t_iseq(state->prsbuf, ','))
     350                 :         101 :                                 statecode = INPOSINFO;
     351   [ +  +  +  +  :        3222 :                         else if (t_iseq(state->prsbuf, 'a') || t_iseq(state->prsbuf, 'A') || t_iseq(state->prsbuf, '*'))
                   +  + ]
     352                 :             :                         {
     353         [ -  + ]:          70 :                                 if (WEP_GETWEIGHT(pos[npos - 1]))
     354                 :           0 :                                         PRSSYNTAXERROR;
     355                 :          70 :                                 WEP_SETWEIGHT(pos[npos - 1], 3);
     356                 :          70 :                         }
     357   [ +  +  +  + ]:        3152 :                         else if (t_iseq(state->prsbuf, 'b') || t_iseq(state->prsbuf, 'B'))
     358                 :             :                         {
     359         [ -  + ]:          36 :                                 if (WEP_GETWEIGHT(pos[npos - 1]))
     360                 :           0 :                                         PRSSYNTAXERROR;
     361                 :          36 :                                 WEP_SETWEIGHT(pos[npos - 1], 2);
     362                 :          36 :                         }
     363   [ +  +  +  + ]:        3116 :                         else if (t_iseq(state->prsbuf, 'c') || t_iseq(state->prsbuf, 'C'))
     364                 :             :                         {
     365         [ +  - ]:          46 :                                 if (WEP_GETWEIGHT(pos[npos - 1]))
     366                 :           0 :                                         PRSSYNTAXERROR;
     367                 :          46 :                                 WEP_SETWEIGHT(pos[npos - 1], 1);
     368                 :          46 :                         }
     369   [ +  +  +  + ]:        3070 :                         else if (t_iseq(state->prsbuf, 'd') || t_iseq(state->prsbuf, 'D'))
     370                 :             :                         {
     371         [ -  + ]:          22 :                                 if (WEP_GETWEIGHT(pos[npos - 1]))
     372                 :           0 :                                         PRSSYNTAXERROR;
     373                 :          22 :                                 WEP_SETWEIGHT(pos[npos - 1], 0);
     374                 :          22 :                         }
     375   [ +  +  +  + ]:        3048 :                         else if (isspace((unsigned char) *state->prsbuf) ||
     376                 :        1467 :                                          *(state->prsbuf) == '\0')
     377   [ +  -  #  #  :        1653 :                                 RETURN_TOKEN;
          +  -  +  -  -  
                      + ]
     378         [ -  + ]:        1395 :                         else if (!isdigit((unsigned char) *state->prsbuf))
     379                 :           0 :                                 PRSSYNTAXERROR;
     380                 :        1670 :                 }
     381                 :             :                 else                                    /* internal error */
     382   [ #  #  #  # ]:           0 :                         elog(ERROR, "unrecognized state in gettoken_tsvector: %d",
     383                 :             :                                  statecode);
     384                 :             : 
     385                 :             :                 /* get next char */
     386                 :       98708 :                 state->prsbuf += pg_mblen(state->prsbuf);
     387                 :             :         }
     388                 :       32112 : }
        

Generated by: LCOV version 2.3.2-1