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 : }
|