Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * tsvector_parser.c
4 : : * Parser for tsvector
5 : : *
6 : : * Portions Copyright (c) 1996-2024, 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
474 tgl@sss.pgh.pa.us 57 :CBC 3816 : init_tsvector_parser(char *input, int flags, Node *escontext)
58 : : {
59 : : TSVectorParseState state;
60 : :
6064 teodor@sigaev.ru 61 : 3816 : state = (TSVectorParseState) palloc(sizeof(struct TSVectorParseStateData));
62 : 3816 : state->prsbuf = input;
6020 tgl@sss.pgh.pa.us 63 : 3816 : state->bufstart = input;
6064 teodor@sigaev.ru 64 : 3816 : state->len = 32;
65 : 3816 : state->word = (char *) palloc(state->len);
6020 tgl@sss.pgh.pa.us 66 : 3816 : state->eml = pg_database_encoding_max_length();
2201 teodor@sigaev.ru 67 : 3816 : state->oprisdelim = (flags & P_TSV_OPR_IS_DELIM) != 0;
68 : 3816 : state->is_tsquery = (flags & P_TSV_IS_TSQUERY) != 0;
69 : 3816 : state->is_web = (flags & P_TSV_IS_WEB) != 0;
474 tgl@sss.pgh.pa.us 70 : 3816 : state->escontext = escontext;
71 : :
6064 teodor@sigaev.ru 72 : 3816 : return state;
73 : : }
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 : 4065 : reset_tsvector_parser(TSVectorParseState state, char *input)
82 : : {
5995 bruce@momjian.us 83 : 4065 : state->prsbuf = input;
6064 teodor@sigaev.ru 84 : 4065 : }
85 : :
86 : : /*
87 : : * Shuts down a tsvector parser.
88 : : */
89 : : void
90 : 3813 : close_tsvector_parser(TSVectorParseState state)
91 : : {
92 : 3813 : pfree(state->word);
93 : 3813 : pfree(state);
94 : 3813 : }
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
6020 tgl@sss.pgh.pa.us 142 : 9 : prssyntaxerror(TSVectorParseState state)
143 : : {
474 144 [ + + - + ]: 9 : 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 : 6 : 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
5995 bruce@momjian.us 176 : 96365 : gettoken_tsvector(TSVectorParseState state,
177 : : char **strval, int *lenval,
178 : : WordEntryPos **pos_ptr, int *poslen,
179 : : char **endptr)
180 : : {
181 : 96365 : int oldstate = 0;
182 : 96365 : char *curpos = state->word;
183 : 96365 : int statecode = WAITWORD;
184 : :
185 : : /*
186 : : * pos is for collecting the comma delimited list of positions followed by
187 : : * the actual token.
188 : : */
6064 teodor@sigaev.ru 189 : 96365 : WordEntryPos *pos = NULL;
5995 bruce@momjian.us 190 : 96365 : int npos = 0; /* elements of pos used */
191 : 96365 : int posalen = 0; /* allocated size of pos */
192 : :
193 : : while (1)
194 : : {
6064 teodor@sigaev.ru 195 [ + + ]: 392574 : if (statecode == WAITWORD)
196 : : {
197 [ + + ]: 185055 : if (*(state->prsbuf) == '\0')
198 : 1898 : return false;
2201 199 [ + + + + ]: 183157 : else if (!state->is_web && t_iseq(state->prsbuf, '\''))
6064 200 : 81 : statecode = WAITENDCMPLX;
2201 201 [ + + + + ]: 183076 : else if (!state->is_web && t_iseq(state->prsbuf, '\\'))
202 : : {
6064 203 : 3 : statecode = WAITNEXTCHAR;
204 : 3 : oldstate = WAITENDWORD;
205 : : }
2201 206 [ + + + - : 183073 : else if ((state->oprisdelim && ISOPERATOR(state->prsbuf)) ||
+ - + - +
- + - + -
+ - ]
207 [ + + - + ]: 183073 : (state->is_web && t_iseq(state->prsbuf, '"')))
6020 tgl@sss.pgh.pa.us 208 :UBC 0 : PRSSYNTAXERROR;
6064 teodor@sigaev.ru 209 [ + + ]:CBC 183073 : else if (!t_isspace(state->prsbuf))
210 : : {
211 : 94383 : COPYCHAR(curpos, state->prsbuf);
212 : 94383 : curpos += pg_mblen(state->prsbuf);
213 : 94383 : statecode = WAITENDWORD;
214 : : }
215 : : }
216 [ + + ]: 207519 : else if (statecode == WAITNEXTCHAR)
217 : : {
218 [ - + ]: 81 : if (*(state->prsbuf) == '\0')
474 tgl@sss.pgh.pa.us 219 [ # # ]:UBC 0 : ereturn(state->escontext, false,
220 : : (errcode(ERRCODE_SYNTAX_ERROR),
221 : : errmsg("there is no escaped character: \"%s\"",
222 : : state->bufstart)));
223 : : else
224 : : {
6064 teodor@sigaev.ru 225 [ - + ]:CBC 81 : RESIZEPRSBUF;
226 : 81 : COPYCHAR(curpos, state->prsbuf);
227 : 81 : curpos += pg_mblen(state->prsbuf);
228 [ - + ]: 81 : Assert(oldstate != 0);
229 : 81 : statecode = oldstate;
230 : : }
231 : : }
232 [ + + ]: 207438 : else if (statecode == WAITENDWORD)
233 : : {
2201 234 [ + + + + ]: 191625 : if (!state->is_web && t_iseq(state->prsbuf, '\\'))
235 : : {
6064 236 : 36 : statecode = WAITNEXTCHAR;
237 : 36 : oldstate = WAITENDWORD;
238 : : }
239 [ + + + + ]: 191589 : else if (t_isspace(state->prsbuf) || *(state->prsbuf) == '\0' ||
2201 240 [ + + + - : 103404 : (state->oprisdelim && ISOPERATOR(state->prsbuf)) ||
+ + + + +
+ + - + +
+ + ]
241 [ + + + + ]: 102516 : (state->is_web && t_iseq(state->prsbuf, '"')))
242 : : {
6064 243 [ - + ]: 89079 : RESIZEPRSBUF;
244 [ - + ]: 89079 : if (curpos == state->word)
6020 tgl@sss.pgh.pa.us 245 :UBC 0 : PRSSYNTAXERROR;
6064 teodor@sigaev.ru 246 :CBC 89079 : *(curpos) = '\0';
247 [ + + - + : 89079 : RETURN_TOKEN;
+ - + - +
+ ]
248 : : }
249 [ + + ]: 102510 : else if (t_iseq(state->prsbuf, ':'))
250 : : {
251 [ - + ]: 5307 : if (curpos == state->word)
6020 tgl@sss.pgh.pa.us 252 :UBC 0 : PRSSYNTAXERROR;
6064 teodor@sigaev.ru 253 :CBC 5307 : *(curpos) = '\0';
254 [ + + ]: 5307 : if (state->oprisdelim)
255 [ - + - + : 348 : RETURN_TOKEN;
+ - + - +
- ]
256 : : else
257 : 4959 : statecode = INPOSINFO;
258 : : }
259 : : else
260 : : {
261 [ - + ]: 97203 : RESIZEPRSBUF;
262 : 97203 : COPYCHAR(curpos, state->prsbuf);
263 : 97203 : curpos += pg_mblen(state->prsbuf);
264 : : }
265 : : }
266 [ + + ]: 15813 : else if (statecode == WAITENDCMPLX)
267 : : {
2201 268 [ + - + + ]: 462 : if (!state->is_web && t_iseq(state->prsbuf, '\''))
269 : : {
6064 270 : 81 : statecode = WAITCHARCMPLX;
271 : : }
2201 272 [ + - + + ]: 381 : else if (!state->is_web && t_iseq(state->prsbuf, '\\'))
273 : : {
6064 274 : 42 : statecode = WAITNEXTCHAR;
275 : 42 : oldstate = WAITENDCMPLX;
276 : : }
277 [ - + ]: 339 : else if (*(state->prsbuf) == '\0')
6020 tgl@sss.pgh.pa.us 278 :UBC 0 : PRSSYNTAXERROR;
279 : : else
280 : : {
6064 teodor@sigaev.ru 281 [ - + ]:CBC 339 : RESIZEPRSBUF;
282 : 339 : COPYCHAR(curpos, state->prsbuf);
283 : 339 : curpos += pg_mblen(state->prsbuf);
284 : : }
285 : : }
286 [ + + ]: 15351 : else if (statecode == WAITCHARCMPLX)
287 : : {
2201 288 [ + - - + ]: 81 : if (!state->is_web && t_iseq(state->prsbuf, '\''))
289 : : {
6064 teodor@sigaev.ru 290 [ # # ]:UBC 0 : RESIZEPRSBUF;
291 : 0 : COPYCHAR(curpos, state->prsbuf);
292 : 0 : curpos += pg_mblen(state->prsbuf);
293 : 0 : statecode = WAITENDCMPLX;
294 : : }
295 : : else
296 : : {
6064 teodor@sigaev.ru 297 [ - + ]:CBC 81 : RESIZEPRSBUF;
298 : 81 : *(curpos) = '\0';
299 [ + + ]: 81 : if (curpos == state->word)
6020 tgl@sss.pgh.pa.us 300 : 9 : PRSSYNTAXERROR;
6064 teodor@sigaev.ru 301 [ + + ]: 72 : if (state->oprisdelim)
302 : : {
303 : : /* state->prsbuf+=pg_mblen(state->prsbuf); */
304 [ - + - + : 33 : RETURN_TOKEN;
+ - + - +
- ]
305 : : }
306 : : else
307 : 39 : statecode = WAITPOSINFO;
308 : 39 : continue; /* recheck current character */
309 : : }
310 : : }
311 [ + + ]: 15270 : else if (statecode == WAITPOSINFO)
312 : : {
313 [ - + ]: 39 : if (t_iseq(state->prsbuf, ':'))
6064 teodor@sigaev.ru 314 :UBC 0 : statecode = INPOSINFO;
315 : : else
6064 teodor@sigaev.ru 316 [ + - - - :CBC 39 : RETURN_TOKEN;
+ - + - -
+ ]
317 : : }
318 [ + + ]: 15231 : else if (statecode == INPOSINFO)
319 : : {
320 [ + - ]: 5262 : if (t_isdigit(state->prsbuf))
321 : : {
322 [ + + ]: 5262 : if (posalen == 0)
323 : : {
324 : 4959 : posalen = 4;
325 : 4959 : pos = (WordEntryPos *) palloc(sizeof(WordEntryPos) * posalen);
326 : 4959 : npos = 0;
327 : : }
328 [ + + ]: 303 : else if (npos + 1 >= posalen)
329 : : {
330 : 57 : posalen *= 2;
331 : 57 : pos = (WordEntryPos *) repalloc(pos, sizeof(WordEntryPos) * posalen);
332 : : }
333 : 5262 : npos++;
334 [ + - ]: 5262 : WEP_SETPOS(pos[npos - 1], LIMITPOS(atoi(state->prsbuf)));
335 : : /* we cannot get here in tsquery, so no need for 2 errmsgs */
336 [ - + ]: 5262 : if (WEP_GETPOS(pos[npos - 1]) == 0)
474 tgl@sss.pgh.pa.us 337 [ # # ]:UBC 0 : ereturn(state->escontext, false,
338 : : (errcode(ERRCODE_SYNTAX_ERROR),
339 : : errmsg("wrong position info in tsvector: \"%s\"",
340 : : state->bufstart)));
6064 teodor@sigaev.ru 341 :CBC 5262 : WEP_SETWEIGHT(pos[npos - 1], 0);
342 : 5262 : statecode = WAITPOSDELIM;
343 : : }
344 : : else
6020 tgl@sss.pgh.pa.us 345 :UBC 0 : PRSSYNTAXERROR;
346 : : }
6064 teodor@sigaev.ru 347 [ + - ]:CBC 9969 : else if (statecode == WAITPOSDELIM)
348 : : {
349 [ + + ]: 9969 : if (t_iseq(state->prsbuf, ','))
350 : 303 : statecode = INPOSINFO;
351 [ + + + + : 9666 : else if (t_iseq(state->prsbuf, 'a') || t_iseq(state->prsbuf, 'A') || t_iseq(state->prsbuf, '*'))
+ + ]
352 : : {
353 [ - + ]: 210 : if (WEP_GETWEIGHT(pos[npos - 1]))
6020 tgl@sss.pgh.pa.us 354 :UBC 0 : PRSSYNTAXERROR;
6064 teodor@sigaev.ru 355 :CBC 210 : WEP_SETWEIGHT(pos[npos - 1], 3);
356 : : }
357 [ + + + + ]: 9456 : else if (t_iseq(state->prsbuf, 'b') || t_iseq(state->prsbuf, 'B'))
358 : : {
359 [ - + ]: 108 : if (WEP_GETWEIGHT(pos[npos - 1]))
6020 tgl@sss.pgh.pa.us 360 :UBC 0 : PRSSYNTAXERROR;
6064 teodor@sigaev.ru 361 :CBC 108 : WEP_SETWEIGHT(pos[npos - 1], 2);
362 : : }
363 [ + + + + ]: 9348 : else if (t_iseq(state->prsbuf, 'c') || t_iseq(state->prsbuf, 'C'))
364 : : {
365 [ - + ]: 138 : if (WEP_GETWEIGHT(pos[npos - 1]))
6020 tgl@sss.pgh.pa.us 366 :UBC 0 : PRSSYNTAXERROR;
6064 teodor@sigaev.ru 367 :CBC 138 : WEP_SETWEIGHT(pos[npos - 1], 1);
368 : : }
369 [ + + + + ]: 9210 : else if (t_iseq(state->prsbuf, 'd') || t_iseq(state->prsbuf, 'D'))
370 : : {
371 [ - + ]: 66 : if (WEP_GETWEIGHT(pos[npos - 1]))
6020 tgl@sss.pgh.pa.us 372 :UBC 0 : PRSSYNTAXERROR;
6064 teodor@sigaev.ru 373 :CBC 66 : WEP_SETWEIGHT(pos[npos - 1], 0);
374 : : }
375 [ + + ]: 9144 : else if (t_isspace(state->prsbuf) ||
376 [ + + ]: 4401 : *(state->prsbuf) == '\0')
377 [ + - - - : 4959 : RETURN_TOKEN;
+ - + - -
+ ]
378 [ - + ]: 4185 : else if (!t_isdigit(state->prsbuf))
6020 tgl@sss.pgh.pa.us 379 :UBC 0 : PRSSYNTAXERROR;
380 : : }
381 : : else /* internal error */
5982 382 [ # # ]: 0 : elog(ERROR, "unrecognized state in gettoken_tsvector: %d",
383 : : statecode);
384 : :
385 : : /* get next char */
6064 teodor@sigaev.ru 386 :CBC 296170 : state->prsbuf += pg_mblen(state->prsbuf);
387 : : }
388 : : }
|