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