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