Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * to_tsany.c
4 : * to_ts* function definitions
5 : *
6 : * Portions Copyright (c) 1996-2017, 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/jsonapi.h"
20 :
21 :
22 : typedef struct MorphOpaque
23 : {
24 : Oid cfg_id;
25 : int qoperator; /* query operator */
26 : } MorphOpaque;
27 :
28 : typedef struct TSVectorBuildState
29 : {
30 : ParsedText *prs;
31 : Oid cfgId;
32 : } TSVectorBuildState;
33 :
34 : static void add_to_tsvector(void *_state, char *elem_value, int elem_len);
35 :
36 :
37 : Datum
38 0 : get_current_ts_config(PG_FUNCTION_ARGS)
39 : {
40 0 : PG_RETURN_OID(getTSCurrentConfig(true));
41 : }
42 :
43 : /*
44 : * to_tsvector
45 : */
46 : static int
47 1799 : compareWORD(const void *a, const void *b)
48 : {
49 : int res;
50 :
51 3598 : res = tsCompareString(
52 1799 : ((const ParsedWord *) a)->word, ((const ParsedWord *) a)->len,
53 1799 : ((const ParsedWord *) b)->word, ((const ParsedWord *) b)->len,
54 : false);
55 :
56 1799 : if (res == 0)
57 : {
58 166 : if (((const ParsedWord *) a)->pos.pos == ((const ParsedWord *) b)->pos.pos)
59 4 : return 0;
60 :
61 162 : res = (((const ParsedWord *) a)->pos.pos > ((const ParsedWord *) b)->pos.pos) ? 1 : -1;
62 : }
63 :
64 1795 : return res;
65 : }
66 :
67 : static int
68 70 : uniqueWORD(ParsedWord *a, int32 l)
69 : {
70 : ParsedWord *ptr,
71 : *res;
72 : int tmppos;
73 :
74 70 : if (l == 1)
75 : {
76 3 : tmppos = LIMITPOS(a->pos.pos);
77 3 : a->alen = 2;
78 3 : a->pos.apos = (uint16 *) palloc(sizeof(uint16) * a->alen);
79 3 : a->pos.apos[0] = 1;
80 3 : a->pos.apos[1] = tmppos;
81 3 : return l;
82 : }
83 :
84 67 : res = a;
85 67 : ptr = a + 1;
86 :
87 : /*
88 : * Sort words with its positions
89 : */
90 67 : qsort((void *) a, l, sizeof(ParsedWord), compareWORD);
91 :
92 : /*
93 : * Initialize first word and its first position
94 : */
95 67 : tmppos = LIMITPOS(a->pos.pos);
96 67 : a->alen = 2;
97 67 : a->pos.apos = (uint16 *) palloc(sizeof(uint16) * a->alen);
98 67 : a->pos.apos[0] = 1;
99 67 : a->pos.apos[1] = tmppos;
100 :
101 : /*
102 : * Summarize position information for each word
103 : */
104 595 : while (ptr - a < l)
105 : {
106 697 : if (!(ptr->len == res->len &&
107 236 : strncmp(ptr->word, res->word, res->len) == 0))
108 : {
109 : /*
110 : * Got a new word, so put it in result
111 : */
112 361 : res++;
113 361 : res->len = ptr->len;
114 361 : res->word = ptr->word;
115 361 : tmppos = LIMITPOS(ptr->pos.pos);
116 361 : res->alen = 2;
117 361 : res->pos.apos = (uint16 *) palloc(sizeof(uint16) * res->alen);
118 361 : res->pos.apos[0] = 1;
119 361 : res->pos.apos[1] = tmppos;
120 : }
121 : else
122 : {
123 : /*
124 : * The word already exists, so adjust position information. But
125 : * before we should check size of position's array, max allowed
126 : * value for position and uniqueness of position
127 : */
128 100 : pfree(ptr->word);
129 200 : if (res->pos.apos[0] < MAXNUMPOS - 1 && res->pos.apos[res->pos.apos[0]] != MAXENTRYPOS - 1 &&
130 100 : res->pos.apos[res->pos.apos[0]] != LIMITPOS(ptr->pos.pos))
131 : {
132 96 : if (res->pos.apos[0] + 1 >= res->alen)
133 : {
134 75 : res->alen *= 2;
135 75 : res->pos.apos = (uint16 *) repalloc(res->pos.apos, sizeof(uint16) * res->alen);
136 : }
137 96 : if (res->pos.apos[0] == 0 || res->pos.apos[res->pos.apos[0]] != LIMITPOS(ptr->pos.pos))
138 : {
139 96 : res->pos.apos[res->pos.apos[0] + 1] = LIMITPOS(ptr->pos.pos);
140 96 : res->pos.apos[0]++;
141 : }
142 : }
143 : }
144 461 : ptr++;
145 : }
146 :
147 67 : return res + 1 - a;
148 : }
149 :
150 : /*
151 : * make value of tsvector, given parsed text
152 : *
153 : * Note: frees prs->words and subsidiary data.
154 : */
155 : TSVector
156 80 : make_tsvector(ParsedText *prs)
157 : {
158 : int i,
159 : j,
160 80 : lenstr = 0,
161 : totallen;
162 : TSVector in;
163 : WordEntry *ptr;
164 : char *str;
165 : int stroff;
166 :
167 : /* Merge duplicate words */
168 80 : if (prs->curwords > 0)
169 70 : prs->curwords = uniqueWORD(prs->words, prs->curwords);
170 :
171 : /* Determine space needed */
172 511 : for (i = 0; i < prs->curwords; i++)
173 : {
174 431 : lenstr += prs->words[i].len;
175 431 : if (prs->words[i].alen)
176 : {
177 431 : lenstr = SHORTALIGN(lenstr);
178 431 : lenstr += sizeof(uint16) + prs->words[i].pos.apos[0] * sizeof(WordEntryPos);
179 : }
180 : }
181 :
182 80 : if (lenstr > MAXSTRPOS)
183 0 : ereport(ERROR,
184 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
185 : errmsg("string is too long for tsvector (%d bytes, max %d bytes)", lenstr, MAXSTRPOS)));
186 :
187 80 : totallen = CALCDATASIZE(prs->curwords, lenstr);
188 80 : in = (TSVector) palloc0(totallen);
189 80 : SET_VARSIZE(in, totallen);
190 80 : in->size = prs->curwords;
191 :
192 80 : ptr = ARRPTR(in);
193 80 : str = STRPTR(in);
194 80 : stroff = 0;
195 511 : for (i = 0; i < prs->curwords; i++)
196 : {
197 431 : ptr->len = prs->words[i].len;
198 431 : ptr->pos = stroff;
199 431 : memcpy(str + stroff, prs->words[i].word, prs->words[i].len);
200 431 : stroff += prs->words[i].len;
201 431 : pfree(prs->words[i].word);
202 431 : if (prs->words[i].alen)
203 : {
204 431 : int k = prs->words[i].pos.apos[0];
205 : WordEntryPos *wptr;
206 :
207 431 : if (k > 0xFFFF)
208 0 : elog(ERROR, "positions array too long");
209 :
210 431 : ptr->haspos = 1;
211 431 : stroff = SHORTALIGN(stroff);
212 431 : *(uint16 *) (str + stroff) = (uint16) k;
213 431 : wptr = POSDATAPTR(in, ptr);
214 958 : for (j = 0; j < k; j++)
215 : {
216 527 : WEP_SETWEIGHT(wptr[j], 0);
217 527 : WEP_SETPOS(wptr[j], prs->words[i].pos.apos[j + 1]);
218 : }
219 431 : stroff += sizeof(uint16) + k * sizeof(WordEntryPos);
220 431 : pfree(prs->words[i].pos.apos);
221 : }
222 : else
223 0 : ptr->haspos = 0;
224 431 : ptr++;
225 : }
226 :
227 80 : if (prs->words)
228 74 : pfree(prs->words);
229 :
230 80 : return in;
231 : }
232 :
233 : Datum
234 63 : to_tsvector_byid(PG_FUNCTION_ARGS)
235 : {
236 63 : Oid cfgId = PG_GETARG_OID(0);
237 63 : text *in = PG_GETARG_TEXT_PP(1);
238 : ParsedText prs;
239 : TSVector out;
240 :
241 63 : prs.lenwords = VARSIZE_ANY_EXHDR(in) / 6; /* just estimation of word's
242 : * number */
243 63 : if (prs.lenwords < 2)
244 42 : prs.lenwords = 2;
245 63 : prs.curwords = 0;
246 63 : prs.pos = 0;
247 63 : prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * prs.lenwords);
248 :
249 63 : parsetext(cfgId, &prs, VARDATA_ANY(in), VARSIZE_ANY_EXHDR(in));
250 :
251 63 : PG_FREE_IF_COPY(in, 1);
252 :
253 63 : out = make_tsvector(&prs);
254 :
255 63 : PG_RETURN_TSVECTOR(out);
256 : }
257 :
258 : Datum
259 9 : to_tsvector(PG_FUNCTION_ARGS)
260 : {
261 9 : text *in = PG_GETARG_TEXT_PP(0);
262 : Oid cfgId;
263 :
264 9 : cfgId = getTSCurrentConfig(true);
265 9 : PG_RETURN_DATUM(DirectFunctionCall2(to_tsvector_byid,
266 : ObjectIdGetDatum(cfgId),
267 : PointerGetDatum(in)));
268 : }
269 :
270 : Datum
271 7 : jsonb_to_tsvector_byid(PG_FUNCTION_ARGS)
272 : {
273 7 : Oid cfgId = PG_GETARG_OID(0);
274 7 : Jsonb *jb = PG_GETARG_JSONB(1);
275 : TSVector result;
276 : TSVectorBuildState state;
277 : ParsedText prs;
278 :
279 7 : prs.words = NULL;
280 7 : prs.curwords = 0;
281 7 : state.prs = &prs;
282 7 : state.cfgId = cfgId;
283 :
284 7 : iterate_jsonb_string_values(jb, &state, add_to_tsvector);
285 :
286 7 : PG_FREE_IF_COPY(jb, 1);
287 :
288 7 : result = make_tsvector(&prs);
289 :
290 7 : PG_RETURN_TSVECTOR(result);
291 : }
292 :
293 : Datum
294 5 : jsonb_to_tsvector(PG_FUNCTION_ARGS)
295 : {
296 5 : Jsonb *jb = PG_GETARG_JSONB(0);
297 : Oid cfgId;
298 :
299 5 : cfgId = getTSCurrentConfig(true);
300 5 : PG_RETURN_DATUM(DirectFunctionCall2(jsonb_to_tsvector_byid,
301 : ObjectIdGetDatum(cfgId),
302 : JsonbGetDatum(jb)));
303 : }
304 :
305 : Datum
306 7 : json_to_tsvector_byid(PG_FUNCTION_ARGS)
307 : {
308 7 : Oid cfgId = PG_GETARG_OID(0);
309 7 : text *json = PG_GETARG_TEXT_P(1);
310 : TSVector result;
311 : TSVectorBuildState state;
312 : ParsedText prs;
313 :
314 7 : prs.words = NULL;
315 7 : prs.curwords = 0;
316 7 : state.prs = &prs;
317 7 : state.cfgId = cfgId;
318 :
319 7 : iterate_json_string_values(json, &state, add_to_tsvector);
320 :
321 7 : PG_FREE_IF_COPY(json, 1);
322 :
323 7 : result = make_tsvector(&prs);
324 :
325 7 : PG_RETURN_TSVECTOR(result);
326 : }
327 :
328 : Datum
329 5 : json_to_tsvector(PG_FUNCTION_ARGS)
330 : {
331 5 : text *json = PG_GETARG_TEXT_P(0);
332 : Oid cfgId;
333 :
334 5 : cfgId = getTSCurrentConfig(true);
335 5 : PG_RETURN_DATUM(DirectFunctionCall2(json_to_tsvector_byid,
336 : ObjectIdGetDatum(cfgId),
337 : PointerGetDatum(json)));
338 : }
339 :
340 : /*
341 : * Parse lexemes in an element of a json(b) value, add to TSVectorBuildState.
342 : */
343 : static void
344 20 : add_to_tsvector(void *_state, char *elem_value, int elem_len)
345 : {
346 20 : TSVectorBuildState *state = (TSVectorBuildState *) _state;
347 20 : ParsedText *prs = state->prs;
348 : int32 prevwords;
349 :
350 20 : if (prs->words == NULL)
351 : {
352 : /*
353 : * First time through: initialize words array to a reasonable size.
354 : * (parsetext() will realloc it bigger as needed.)
355 : */
356 8 : prs->lenwords = Max(elem_len / 6, 64);
357 8 : prs->words = (ParsedWord *) palloc(sizeof(ParsedWord) * prs->lenwords);
358 8 : prs->curwords = 0;
359 8 : prs->pos = 0;
360 : }
361 :
362 20 : prevwords = prs->curwords;
363 :
364 20 : parsetext(state->cfgId, prs, elem_value, elem_len);
365 :
366 : /*
367 : * If we extracted any words from this JSON element, advance pos to create
368 : * an artificial break between elements. This is because we don't want
369 : * phrase searches to think that the last word in this element is adjacent
370 : * to the first word in the next one.
371 : */
372 20 : if (prs->curwords > prevwords)
373 18 : prs->pos += 1;
374 20 : }
375 :
376 :
377 : /*
378 : * to_tsquery
379 : */
380 :
381 :
382 : /*
383 : * This function is used for morph parsing.
384 : *
385 : * The value is passed to parsetext which will call the right dictionary to
386 : * lexize the word. If it turns out to be a stopword, we push a QI_VALSTOP
387 : * to the stack.
388 : *
389 : * All words belonging to the same variant are pushed as an ANDed list,
390 : * and different variants are ORed together.
391 : */
392 : static void
393 312 : pushval_morph(Datum opaque, TSQueryParserState state, char *strval, int lenval, int16 weight, bool prefix)
394 : {
395 312 : int32 count = 0;
396 : ParsedText prs;
397 : uint32 variant,
398 312 : pos = 0,
399 312 : cntvar = 0,
400 312 : cntpos = 0,
401 312 : cnt = 0;
402 312 : MorphOpaque *data = (MorphOpaque *) DatumGetPointer(opaque);
403 :
404 312 : prs.lenwords = 4;
405 312 : prs.curwords = 0;
406 312 : prs.pos = 0;
407 312 : prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * prs.lenwords);
408 :
409 312 : parsetext(data->cfg_id, &prs, strval, lenval);
410 :
411 312 : if (prs.curwords > 0)
412 : {
413 763 : while (count < prs.curwords)
414 : {
415 : /*
416 : * Were any stop words removed? If so, fill empty positions with
417 : * placeholders linked by an appropriate operator.
418 : */
419 271 : if (pos > 0 && pos + 1 < prs.words[count].pos.pos)
420 : {
421 18 : while (pos + 1 < prs.words[count].pos.pos)
422 : {
423 : /* put placeholders for each missing stop word */
424 8 : pushStop(state);
425 8 : if (cntpos)
426 8 : pushOperator(state, data->qoperator, 1);
427 8 : cntpos++;
428 8 : pos++;
429 : }
430 : }
431 :
432 : /* save current word's position */
433 271 : pos = prs.words[count].pos.pos;
434 :
435 : /* Go through all variants obtained from this token */
436 271 : cntvar = 0;
437 825 : while (count < prs.curwords && pos == prs.words[count].pos.pos)
438 : {
439 283 : variant = prs.words[count].nvariant;
440 :
441 : /* Push all words belonging to the same variant */
442 283 : cnt = 0;
443 1217 : while (count < prs.curwords &&
444 663 : pos == prs.words[count].pos.pos &&
445 319 : variant == prs.words[count].nvariant)
446 : {
447 921 : pushValue(state,
448 307 : prs.words[count].word,
449 307 : prs.words[count].len,
450 : weight,
451 307 : ((prs.words[count].flags & TSL_PREFIX) || prefix));
452 307 : pfree(prs.words[count].word);
453 307 : if (cnt)
454 24 : pushOperator(state, OP_AND, 0);
455 307 : cnt++;
456 307 : count++;
457 : }
458 :
459 283 : if (cntvar)
460 12 : pushOperator(state, OP_OR, 0);
461 283 : cntvar++;
462 : }
463 :
464 271 : if (cntpos)
465 : {
466 : /* distance may be useful */
467 25 : pushOperator(state, data->qoperator, 1);
468 : }
469 :
470 271 : cntpos++;
471 : }
472 :
473 246 : pfree(prs.words);
474 :
475 : }
476 : else
477 66 : pushStop(state);
478 312 : }
479 :
480 : Datum
481 114 : to_tsquery_byid(PG_FUNCTION_ARGS)
482 : {
483 114 : text *in = PG_GETARG_TEXT_PP(1);
484 : TSQuery query;
485 : MorphOpaque data;
486 :
487 114 : data.cfg_id = PG_GETARG_OID(0);
488 114 : data.qoperator = OP_AND;
489 :
490 114 : query = parse_tsquery(text_to_cstring(in),
491 : pushval_morph,
492 : PointerGetDatum(&data),
493 : false);
494 :
495 114 : PG_RETURN_TSQUERY(query);
496 : }
497 :
498 : Datum
499 22 : to_tsquery(PG_FUNCTION_ARGS)
500 : {
501 22 : text *in = PG_GETARG_TEXT_PP(0);
502 : Oid cfgId;
503 :
504 22 : cfgId = getTSCurrentConfig(true);
505 22 : PG_RETURN_DATUM(DirectFunctionCall2(to_tsquery_byid,
506 : ObjectIdGetDatum(cfgId),
507 : PointerGetDatum(in)));
508 : }
509 :
510 : Datum
511 10 : plainto_tsquery_byid(PG_FUNCTION_ARGS)
512 : {
513 10 : text *in = PG_GETARG_TEXT_PP(1);
514 : TSQuery query;
515 : MorphOpaque data;
516 :
517 10 : data.cfg_id = PG_GETARG_OID(0);
518 10 : data.qoperator = OP_AND;
519 :
520 10 : query = parse_tsquery(text_to_cstring(in),
521 : pushval_morph,
522 : PointerGetDatum(&data),
523 : true);
524 :
525 10 : PG_RETURN_POINTER(query);
526 : }
527 :
528 : Datum
529 2 : plainto_tsquery(PG_FUNCTION_ARGS)
530 : {
531 2 : text *in = PG_GETARG_TEXT_PP(0);
532 : Oid cfgId;
533 :
534 2 : cfgId = getTSCurrentConfig(true);
535 2 : PG_RETURN_DATUM(DirectFunctionCall2(plainto_tsquery_byid,
536 : ObjectIdGetDatum(cfgId),
537 : PointerGetDatum(in)));
538 : }
539 :
540 :
541 : Datum
542 5 : phraseto_tsquery_byid(PG_FUNCTION_ARGS)
543 : {
544 5 : text *in = PG_GETARG_TEXT_PP(1);
545 : TSQuery query;
546 : MorphOpaque data;
547 :
548 5 : data.cfg_id = PG_GETARG_OID(0);
549 5 : data.qoperator = OP_PHRASE;
550 :
551 5 : query = parse_tsquery(text_to_cstring(in),
552 : pushval_morph,
553 : PointerGetDatum(&data),
554 : true);
555 :
556 5 : PG_RETURN_TSQUERY(query);
557 : }
558 :
559 : Datum
560 0 : phraseto_tsquery(PG_FUNCTION_ARGS)
561 : {
562 0 : text *in = PG_GETARG_TEXT_PP(0);
563 : Oid cfgId;
564 :
565 0 : cfgId = getTSCurrentConfig(true);
566 0 : PG_RETURN_DATUM(DirectFunctionCall2(phraseto_tsquery_byid,
567 : ObjectIdGetDatum(cfgId),
568 : PointerGetDatum(in)));
569 : }
|