Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * to_tsany.c
4 : * to_ts* function definitions
5 : *
6 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
7 : *
8 : *
9 : * IDENTIFICATION
10 : * src/backend/tsearch/to_tsany.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 : #include "postgres.h"
15 :
16 : #include "common/jsonapi.h"
17 : #include "tsearch/ts_cache.h"
18 : #include "tsearch/ts_utils.h"
19 : #include "utils/builtins.h"
20 : #include "utils/jsonfuncs.h"
21 :
22 :
23 : /*
24 : * Opaque data structure, which is passed by parse_tsquery() to pushval_morph().
25 : */
26 : typedef struct MorphOpaque
27 : {
28 : Oid cfg_id;
29 :
30 : /*
31 : * Single tsquery morph could be parsed into multiple words. When these
32 : * words reside in adjacent positions, they are connected using this
33 : * operator. Usually, that is OP_PHRASE, which requires word positions of
34 : * a complex morph to exactly match the tsvector.
35 : */
36 : int qoperator;
37 : } MorphOpaque;
38 :
39 : typedef struct TSVectorBuildState
40 : {
41 : ParsedText *prs;
42 : Oid cfgId;
43 : } TSVectorBuildState;
44 :
45 : static void add_to_tsvector(void *_state, char *elem_value, int elem_len);
46 :
47 :
48 : Datum
49 0 : get_current_ts_config(PG_FUNCTION_ARGS)
50 : {
51 0 : PG_RETURN_OID(getTSCurrentConfig(true));
52 : }
53 :
54 : /*
55 : * to_tsvector
56 : */
57 : static int
58 12458 : compareWORD(const void *a, const void *b)
59 : {
60 : int res;
61 :
62 12458 : res = tsCompareString(((const ParsedWord *) a)->word, ((const ParsedWord *) a)->len,
63 12458 : ((const ParsedWord *) b)->word, ((const ParsedWord *) b)->len,
64 : false);
65 :
66 12458 : if (res == 0)
67 : {
68 1050 : if (((const ParsedWord *) a)->pos.pos == ((const ParsedWord *) b)->pos.pos)
69 24 : return 0;
70 :
71 1026 : res = (((const ParsedWord *) a)->pos.pos > ((const ParsedWord *) b)->pos.pos) ? 1 : -1;
72 : }
73 :
74 12434 : return res;
75 : }
76 :
77 : static int
78 676 : uniqueWORD(ParsedWord *a, int32 l)
79 : {
80 : ParsedWord *ptr,
81 : *res;
82 : int tmppos;
83 :
84 676 : if (l == 1)
85 : {
86 26 : tmppos = LIMITPOS(a->pos.pos);
87 26 : a->alen = 2;
88 26 : a->pos.apos = (uint16 *) palloc(sizeof(uint16) * a->alen);
89 26 : a->pos.apos[0] = 1;
90 26 : a->pos.apos[1] = tmppos;
91 26 : return l;
92 : }
93 :
94 650 : res = a;
95 650 : ptr = a + 1;
96 :
97 : /*
98 : * Sort words with its positions
99 : */
100 650 : qsort(a, l, sizeof(ParsedWord), compareWORD);
101 :
102 : /*
103 : * Initialize first word and its first position
104 : */
105 650 : tmppos = LIMITPOS(a->pos.pos);
106 650 : a->alen = 2;
107 650 : a->pos.apos = (uint16 *) palloc(sizeof(uint16) * a->alen);
108 650 : a->pos.apos[0] = 1;
109 650 : a->pos.apos[1] = tmppos;
110 :
111 : /*
112 : * Summarize position information for each word
113 : */
114 4192 : while (ptr - a < l)
115 : {
116 3542 : if (!(ptr->len == res->len &&
117 2030 : strncmp(ptr->word, res->word, res->len) == 0))
118 : {
119 : /*
120 : * Got a new word, so put it in result
121 : */
122 2888 : res++;
123 2888 : res->len = ptr->len;
124 2888 : res->word = ptr->word;
125 2888 : tmppos = LIMITPOS(ptr->pos.pos);
126 2888 : res->alen = 2;
127 2888 : res->pos.apos = (uint16 *) palloc(sizeof(uint16) * res->alen);
128 2888 : res->pos.apos[0] = 1;
129 2888 : res->pos.apos[1] = tmppos;
130 : }
131 : else
132 : {
133 : /*
134 : * The word already exists, so adjust position information. But
135 : * before we should check size of position's array, max allowed
136 : * value for position and uniqueness of position
137 : */
138 654 : pfree(ptr->word);
139 654 : if (res->pos.apos[0] < MAXNUMPOS - 1 && res->pos.apos[res->pos.apos[0]] != MAXENTRYPOS - 1 &&
140 654 : res->pos.apos[res->pos.apos[0]] != LIMITPOS(ptr->pos.pos))
141 : {
142 630 : if (res->pos.apos[0] + 1 >= res->alen)
143 : {
144 504 : res->alen *= 2;
145 504 : res->pos.apos = (uint16 *) repalloc(res->pos.apos, sizeof(uint16) * res->alen);
146 : }
147 630 : if (res->pos.apos[0] == 0 || res->pos.apos[res->pos.apos[0]] != LIMITPOS(ptr->pos.pos))
148 : {
149 630 : res->pos.apos[res->pos.apos[0] + 1] = LIMITPOS(ptr->pos.pos);
150 630 : res->pos.apos[0]++;
151 : }
152 : }
153 : }
154 3542 : ptr++;
155 : }
156 :
157 650 : return res + 1 - a;
158 : }
159 :
160 : /*
161 : * make value of tsvector, given parsed text
162 : *
163 : * Note: frees prs->words and subsidiary data.
164 : */
165 : TSVector
166 796 : make_tsvector(ParsedText *prs)
167 : {
168 : int i,
169 : j,
170 796 : lenstr = 0,
171 : totallen;
172 : TSVector in;
173 : WordEntry *ptr;
174 : char *str;
175 : int stroff;
176 :
177 : /* Merge duplicate words */
178 796 : if (prs->curwords > 0)
179 676 : prs->curwords = uniqueWORD(prs->words, prs->curwords);
180 :
181 : /* Determine space needed */
182 4360 : for (i = 0; i < prs->curwords; i++)
183 : {
184 3564 : lenstr += prs->words[i].len;
185 3564 : if (prs->words[i].alen)
186 : {
187 3564 : lenstr = SHORTALIGN(lenstr);
188 3564 : lenstr += sizeof(uint16) + prs->words[i].pos.apos[0] * sizeof(WordEntryPos);
189 : }
190 : }
191 :
192 796 : if (lenstr > MAXSTRPOS)
193 0 : ereport(ERROR,
194 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
195 : errmsg("string is too long for tsvector (%d bytes, max %d bytes)", lenstr, MAXSTRPOS)));
196 :
197 796 : totallen = CALCDATASIZE(prs->curwords, lenstr);
198 796 : in = (TSVector) palloc0(totallen);
199 796 : SET_VARSIZE(in, totallen);
200 796 : in->size = prs->curwords;
201 :
202 796 : ptr = ARRPTR(in);
203 796 : str = STRPTR(in);
204 796 : stroff = 0;
205 4360 : for (i = 0; i < prs->curwords; i++)
206 : {
207 3564 : ptr->len = prs->words[i].len;
208 3564 : ptr->pos = stroff;
209 3564 : memcpy(str + stroff, prs->words[i].word, prs->words[i].len);
210 3564 : stroff += prs->words[i].len;
211 3564 : pfree(prs->words[i].word);
212 3564 : if (prs->words[i].alen)
213 : {
214 3564 : int k = prs->words[i].pos.apos[0];
215 : WordEntryPos *wptr;
216 :
217 3564 : if (k > 0xFFFF)
218 0 : elog(ERROR, "positions array too long");
219 :
220 3564 : ptr->haspos = 1;
221 3564 : stroff = SHORTALIGN(stroff);
222 3564 : *(uint16 *) (str + stroff) = (uint16) k;
223 3564 : wptr = POSDATAPTR(in, ptr);
224 7758 : for (j = 0; j < k; j++)
225 : {
226 4194 : WEP_SETWEIGHT(wptr[j], 0);
227 4194 : WEP_SETPOS(wptr[j], prs->words[i].pos.apos[j + 1]);
228 : }
229 3564 : stroff += sizeof(uint16) + k * sizeof(WordEntryPos);
230 3564 : pfree(prs->words[i].pos.apos);
231 : }
232 : else
233 0 : ptr->haspos = 0;
234 3564 : ptr++;
235 : }
236 :
237 796 : if (prs->words)
238 712 : pfree(prs->words);
239 :
240 796 : return in;
241 : }
242 :
243 : Datum
244 478 : to_tsvector_byid(PG_FUNCTION_ARGS)
245 : {
246 478 : Oid cfgId = PG_GETARG_OID(0);
247 478 : text *in = PG_GETARG_TEXT_PP(1);
248 : ParsedText prs;
249 : TSVector out;
250 :
251 478 : prs.lenwords = VARSIZE_ANY_EXHDR(in) / 6; /* just estimation of word's
252 : * number */
253 478 : if (prs.lenwords < 2)
254 338 : prs.lenwords = 2;
255 140 : else if (prs.lenwords > MaxAllocSize / sizeof(ParsedWord))
256 0 : prs.lenwords = MaxAllocSize / sizeof(ParsedWord);
257 478 : prs.curwords = 0;
258 478 : prs.pos = 0;
259 478 : prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * prs.lenwords);
260 :
261 478 : parsetext(cfgId, &prs, VARDATA_ANY(in), VARSIZE_ANY_EXHDR(in));
262 :
263 478 : PG_FREE_IF_COPY(in, 1);
264 :
265 478 : out = make_tsvector(&prs);
266 :
267 478 : PG_RETURN_TSVECTOR(out);
268 : }
269 :
270 : Datum
271 54 : to_tsvector(PG_FUNCTION_ARGS)
272 : {
273 54 : text *in = PG_GETARG_TEXT_PP(0);
274 : Oid cfgId;
275 :
276 54 : cfgId = getTSCurrentConfig(true);
277 54 : PG_RETURN_DATUM(DirectFunctionCall2(to_tsvector_byid,
278 : ObjectIdGetDatum(cfgId),
279 : PointerGetDatum(in)));
280 : }
281 :
282 : /*
283 : * Worker function for jsonb(_string)_to_tsvector(_byid)
284 : */
285 : static TSVector
286 150 : jsonb_to_tsvector_worker(Oid cfgId, Jsonb *jb, uint32 flags)
287 : {
288 : TSVectorBuildState state;
289 : ParsedText prs;
290 :
291 150 : prs.words = NULL;
292 150 : prs.curwords = 0;
293 150 : state.prs = &prs;
294 150 : state.cfgId = cfgId;
295 :
296 150 : iterate_jsonb_values(jb, flags, &state, add_to_tsvector);
297 :
298 150 : return make_tsvector(&prs);
299 : }
300 :
301 : Datum
302 18 : jsonb_string_to_tsvector_byid(PG_FUNCTION_ARGS)
303 : {
304 18 : Oid cfgId = PG_GETARG_OID(0);
305 18 : Jsonb *jb = PG_GETARG_JSONB_P(1);
306 : TSVector result;
307 :
308 18 : result = jsonb_to_tsvector_worker(cfgId, jb, jtiString);
309 18 : PG_FREE_IF_COPY(jb, 1);
310 :
311 18 : PG_RETURN_TSVECTOR(result);
312 : }
313 :
314 : Datum
315 30 : jsonb_string_to_tsvector(PG_FUNCTION_ARGS)
316 : {
317 30 : Jsonb *jb = PG_GETARG_JSONB_P(0);
318 : Oid cfgId;
319 : TSVector result;
320 :
321 30 : cfgId = getTSCurrentConfig(true);
322 30 : result = jsonb_to_tsvector_worker(cfgId, jb, jtiString);
323 30 : PG_FREE_IF_COPY(jb, 0);
324 :
325 30 : PG_RETURN_TSVECTOR(result);
326 : }
327 :
328 : Datum
329 102 : jsonb_to_tsvector_byid(PG_FUNCTION_ARGS)
330 : {
331 102 : Oid cfgId = PG_GETARG_OID(0);
332 102 : Jsonb *jb = PG_GETARG_JSONB_P(1);
333 102 : Jsonb *jbFlags = PG_GETARG_JSONB_P(2);
334 : TSVector result;
335 102 : uint32 flags = parse_jsonb_index_flags(jbFlags);
336 :
337 78 : result = jsonb_to_tsvector_worker(cfgId, jb, flags);
338 78 : PG_FREE_IF_COPY(jb, 1);
339 78 : PG_FREE_IF_COPY(jbFlags, 2);
340 :
341 78 : PG_RETURN_TSVECTOR(result);
342 : }
343 :
344 : Datum
345 24 : jsonb_to_tsvector(PG_FUNCTION_ARGS)
346 : {
347 24 : Jsonb *jb = PG_GETARG_JSONB_P(0);
348 24 : Jsonb *jbFlags = PG_GETARG_JSONB_P(1);
349 : Oid cfgId;
350 : TSVector result;
351 24 : uint32 flags = parse_jsonb_index_flags(jbFlags);
352 :
353 24 : cfgId = getTSCurrentConfig(true);
354 24 : result = jsonb_to_tsvector_worker(cfgId, jb, flags);
355 24 : PG_FREE_IF_COPY(jb, 0);
356 24 : PG_FREE_IF_COPY(jbFlags, 1);
357 :
358 24 : PG_RETURN_TSVECTOR(result);
359 : }
360 :
361 : /*
362 : * Worker function for json(_string)_to_tsvector(_byid)
363 : */
364 : static TSVector
365 150 : json_to_tsvector_worker(Oid cfgId, text *json, uint32 flags)
366 : {
367 : TSVectorBuildState state;
368 : ParsedText prs;
369 :
370 150 : prs.words = NULL;
371 150 : prs.curwords = 0;
372 150 : state.prs = &prs;
373 150 : state.cfgId = cfgId;
374 :
375 150 : iterate_json_values(json, flags, &state, add_to_tsvector);
376 :
377 150 : return make_tsvector(&prs);
378 : }
379 :
380 : Datum
381 18 : json_string_to_tsvector_byid(PG_FUNCTION_ARGS)
382 : {
383 18 : Oid cfgId = PG_GETARG_OID(0);
384 18 : text *json = PG_GETARG_TEXT_P(1);
385 : TSVector result;
386 :
387 18 : result = json_to_tsvector_worker(cfgId, json, jtiString);
388 18 : PG_FREE_IF_COPY(json, 1);
389 :
390 18 : PG_RETURN_TSVECTOR(result);
391 : }
392 :
393 : Datum
394 30 : json_string_to_tsvector(PG_FUNCTION_ARGS)
395 : {
396 30 : text *json = PG_GETARG_TEXT_P(0);
397 : Oid cfgId;
398 : TSVector result;
399 :
400 30 : cfgId = getTSCurrentConfig(true);
401 30 : result = json_to_tsvector_worker(cfgId, json, jtiString);
402 30 : PG_FREE_IF_COPY(json, 0);
403 :
404 30 : PG_RETURN_TSVECTOR(result);
405 : }
406 :
407 : Datum
408 102 : json_to_tsvector_byid(PG_FUNCTION_ARGS)
409 : {
410 102 : Oid cfgId = PG_GETARG_OID(0);
411 102 : text *json = PG_GETARG_TEXT_P(1);
412 102 : Jsonb *jbFlags = PG_GETARG_JSONB_P(2);
413 : TSVector result;
414 102 : uint32 flags = parse_jsonb_index_flags(jbFlags);
415 :
416 78 : result = json_to_tsvector_worker(cfgId, json, flags);
417 78 : PG_FREE_IF_COPY(json, 1);
418 78 : PG_FREE_IF_COPY(jbFlags, 2);
419 :
420 78 : PG_RETURN_TSVECTOR(result);
421 : }
422 :
423 : Datum
424 24 : json_to_tsvector(PG_FUNCTION_ARGS)
425 : {
426 24 : text *json = PG_GETARG_TEXT_P(0);
427 24 : Jsonb *jbFlags = PG_GETARG_JSONB_P(1);
428 : Oid cfgId;
429 : TSVector result;
430 24 : uint32 flags = parse_jsonb_index_flags(jbFlags);
431 :
432 24 : cfgId = getTSCurrentConfig(true);
433 24 : result = json_to_tsvector_worker(cfgId, json, flags);
434 24 : PG_FREE_IF_COPY(json, 0);
435 24 : PG_FREE_IF_COPY(jbFlags, 1);
436 :
437 24 : PG_RETURN_TSVECTOR(result);
438 : }
439 :
440 : /*
441 : * Parse lexemes in an element of a json(b) value, add to TSVectorBuildState.
442 : */
443 : static void
444 744 : add_to_tsvector(void *_state, char *elem_value, int elem_len)
445 : {
446 744 : TSVectorBuildState *state = (TSVectorBuildState *) _state;
447 744 : ParsedText *prs = state->prs;
448 : int32 prevwords;
449 :
450 744 : if (prs->words == NULL)
451 : {
452 : /*
453 : * First time through: initialize words array to a reasonable size.
454 : * (parsetext() will realloc it bigger as needed.)
455 : */
456 216 : prs->lenwords = 16;
457 216 : prs->words = (ParsedWord *) palloc(sizeof(ParsedWord) * prs->lenwords);
458 216 : prs->curwords = 0;
459 216 : prs->pos = 0;
460 : }
461 :
462 744 : prevwords = prs->curwords;
463 :
464 744 : parsetext(state->cfgId, prs, elem_value, elem_len);
465 :
466 : /*
467 : * If we extracted any words from this JSON element, advance pos to create
468 : * an artificial break between elements. This is because we don't want
469 : * phrase searches to think that the last word in this element is adjacent
470 : * to the first word in the next one.
471 : */
472 744 : if (prs->curwords > prevwords)
473 672 : prs->pos += 1;
474 744 : }
475 :
476 :
477 : /*
478 : * to_tsquery
479 : */
480 :
481 :
482 : /*
483 : * This function is used for morph parsing.
484 : *
485 : * The value is passed to parsetext which will call the right dictionary to
486 : * lexize the word. If it turns out to be a stopword, we push a QI_VALSTOP
487 : * to the stack.
488 : *
489 : * All words belonging to the same variant are pushed as an ANDed list,
490 : * and different variants are ORed together.
491 : */
492 : static void
493 3088 : pushval_morph(Datum opaque, TSQueryParserState state, char *strval, int lenval, int16 weight, bool prefix)
494 : {
495 3088 : int32 count = 0;
496 : ParsedText prs;
497 : uint32 variant,
498 3088 : pos = 0,
499 3088 : cntvar = 0,
500 3088 : cntpos = 0,
501 3088 : cnt = 0;
502 3088 : MorphOpaque *data = (MorphOpaque *) DatumGetPointer(opaque);
503 :
504 3088 : prs.lenwords = 4;
505 3088 : prs.curwords = 0;
506 3088 : prs.pos = 0;
507 3088 : prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * prs.lenwords);
508 :
509 3088 : parsetext(data->cfg_id, &prs, strval, lenval);
510 :
511 3088 : if (prs.curwords > 0)
512 : {
513 5348 : while (count < prs.curwords)
514 : {
515 : /*
516 : * Were any stop words removed? If so, fill empty positions with
517 : * placeholders linked by an appropriate operator.
518 : */
519 2878 : if (pos > 0 && pos + 1 < prs.words[count].pos.pos)
520 : {
521 78 : while (pos + 1 < prs.words[count].pos.pos)
522 : {
523 : /* put placeholders for each missing stop word */
524 48 : pushStop(state);
525 48 : if (cntpos)
526 48 : pushOperator(state, data->qoperator, 1);
527 48 : cntpos++;
528 48 : pos++;
529 : }
530 : }
531 :
532 : /* save current word's position */
533 2878 : pos = prs.words[count].pos.pos;
534 :
535 : /* Go through all variants obtained from this token */
536 2878 : cntvar = 0;
537 5828 : while (count < prs.curwords && pos == prs.words[count].pos.pos)
538 : {
539 2950 : variant = prs.words[count].nvariant;
540 :
541 : /* Push all words belonging to the same variant */
542 2950 : cnt = 0;
543 6044 : while (count < prs.curwords &&
544 3574 : pos == prs.words[count].pos.pos &&
545 3166 : variant == prs.words[count].nvariant)
546 : {
547 3094 : pushValue(state,
548 3094 : prs.words[count].word,
549 3094 : prs.words[count].len,
550 : weight,
551 3094 : ((prs.words[count].flags & TSL_PREFIX) || prefix));
552 3094 : pfree(prs.words[count].word);
553 3094 : if (cnt)
554 144 : pushOperator(state, OP_AND, 0);
555 3094 : cnt++;
556 3094 : count++;
557 : }
558 :
559 2950 : if (cntvar)
560 72 : pushOperator(state, OP_OR, 0);
561 2950 : cntvar++;
562 : }
563 :
564 2878 : if (cntpos)
565 : {
566 : /* distance may be useful */
567 408 : pushOperator(state, data->qoperator, 1);
568 : }
569 :
570 2878 : cntpos++;
571 : }
572 :
573 2470 : pfree(prs.words);
574 : }
575 : else
576 618 : pushStop(state);
577 3088 : }
578 :
579 : Datum
580 754 : to_tsquery_byid(PG_FUNCTION_ARGS)
581 : {
582 754 : text *in = PG_GETARG_TEXT_PP(1);
583 : TSQuery query;
584 : MorphOpaque data;
585 :
586 754 : data.cfg_id = PG_GETARG_OID(0);
587 :
588 : /*
589 : * Passing OP_PHRASE as a qoperator makes tsquery require matching of word
590 : * positions of a complex morph exactly match the tsvector. Also, when
591 : * the complex morphs are connected with OP_PHRASE operator, we connect
592 : * all their words into the OP_PHRASE sequence.
593 : */
594 754 : data.qoperator = OP_PHRASE;
595 :
596 754 : query = parse_tsquery(text_to_cstring(in),
597 : pushval_morph,
598 : PointerGetDatum(&data),
599 : 0,
600 : NULL);
601 :
602 754 : PG_RETURN_TSQUERY(query);
603 : }
604 :
605 : Datum
606 132 : to_tsquery(PG_FUNCTION_ARGS)
607 : {
608 132 : text *in = PG_GETARG_TEXT_PP(0);
609 : Oid cfgId;
610 :
611 132 : cfgId = getTSCurrentConfig(true);
612 132 : PG_RETURN_DATUM(DirectFunctionCall2(to_tsquery_byid,
613 : ObjectIdGetDatum(cfgId),
614 : PointerGetDatum(in)));
615 : }
616 :
617 : Datum
618 60 : plainto_tsquery_byid(PG_FUNCTION_ARGS)
619 : {
620 60 : text *in = PG_GETARG_TEXT_PP(1);
621 : TSQuery query;
622 : MorphOpaque data;
623 :
624 60 : data.cfg_id = PG_GETARG_OID(0);
625 :
626 : /*
627 : * parse_tsquery() with P_TSQ_PLAIN flag takes the whole input text as a
628 : * single morph. Passing OP_PHRASE as a qoperator makes tsquery require
629 : * matching of all words independently on their positions.
630 : */
631 60 : data.qoperator = OP_AND;
632 :
633 60 : query = parse_tsquery(text_to_cstring(in),
634 : pushval_morph,
635 : PointerGetDatum(&data),
636 : P_TSQ_PLAIN,
637 : NULL);
638 :
639 60 : PG_RETURN_POINTER(query);
640 : }
641 :
642 : Datum
643 12 : plainto_tsquery(PG_FUNCTION_ARGS)
644 : {
645 12 : text *in = PG_GETARG_TEXT_PP(0);
646 : Oid cfgId;
647 :
648 12 : cfgId = getTSCurrentConfig(true);
649 12 : PG_RETURN_DATUM(DirectFunctionCall2(plainto_tsquery_byid,
650 : ObjectIdGetDatum(cfgId),
651 : PointerGetDatum(in)));
652 : }
653 :
654 :
655 : Datum
656 48 : phraseto_tsquery_byid(PG_FUNCTION_ARGS)
657 : {
658 48 : text *in = PG_GETARG_TEXT_PP(1);
659 : TSQuery query;
660 : MorphOpaque data;
661 :
662 48 : data.cfg_id = PG_GETARG_OID(0);
663 :
664 : /*
665 : * parse_tsquery() with P_TSQ_PLAIN flag takes the whole input text as a
666 : * single morph. Passing OP_PHRASE as a qoperator makes tsquery require
667 : * matching of word positions.
668 : */
669 48 : data.qoperator = OP_PHRASE;
670 :
671 48 : query = parse_tsquery(text_to_cstring(in),
672 : pushval_morph,
673 : PointerGetDatum(&data),
674 : P_TSQ_PLAIN,
675 : NULL);
676 :
677 48 : PG_RETURN_TSQUERY(query);
678 : }
679 :
680 : Datum
681 0 : phraseto_tsquery(PG_FUNCTION_ARGS)
682 : {
683 0 : text *in = PG_GETARG_TEXT_PP(0);
684 : Oid cfgId;
685 :
686 0 : cfgId = getTSCurrentConfig(true);
687 0 : PG_RETURN_DATUM(DirectFunctionCall2(phraseto_tsquery_byid,
688 : ObjectIdGetDatum(cfgId),
689 : PointerGetDatum(in)));
690 : }
691 :
692 : Datum
693 408 : websearch_to_tsquery_byid(PG_FUNCTION_ARGS)
694 : {
695 408 : text *in = PG_GETARG_TEXT_PP(1);
696 : MorphOpaque data;
697 408 : TSQuery query = NULL;
698 :
699 408 : data.cfg_id = PG_GETARG_OID(0);
700 :
701 : /*
702 : * Passing OP_PHRASE as a qoperator makes tsquery require matching of word
703 : * positions of a complex morph exactly match the tsvector. Also, when
704 : * the complex morphs are given in quotes, we connect all their words into
705 : * the OP_PHRASE sequence.
706 : */
707 408 : data.qoperator = OP_PHRASE;
708 :
709 408 : query = parse_tsquery(text_to_cstring(in),
710 : pushval_morph,
711 : PointerGetDatum(&data),
712 : P_TSQ_WEB,
713 : NULL);
714 :
715 408 : PG_RETURN_TSQUERY(query);
716 : }
717 :
718 : Datum
719 24 : websearch_to_tsquery(PG_FUNCTION_ARGS)
720 : {
721 24 : text *in = PG_GETARG_TEXT_PP(0);
722 : Oid cfgId;
723 :
724 24 : cfgId = getTSCurrentConfig(true);
725 24 : PG_RETURN_DATUM(DirectFunctionCall2(websearch_to_tsquery_byid,
726 : ObjectIdGetDatum(cfgId),
727 : PointerGetDatum(in)));
728 : }
|