Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * ts_parse.c
4 : * main parse functions for tsearch
5 : *
6 : * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
7 : *
8 : *
9 : * IDENTIFICATION
10 : * src/backend/tsearch/ts_parse.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 :
15 : #include "postgres.h"
16 :
17 : #include "tsearch/ts_cache.h"
18 : #include "tsearch/ts_utils.h"
19 :
20 : #define IGNORE_LONGLEXEME 1
21 :
22 : /*
23 : * Lexize subsystem
24 : */
25 :
26 : typedef struct ParsedLex
27 : {
28 : int type;
29 : char *lemm;
30 : int lenlemm;
31 : struct ParsedLex *next;
32 : } ParsedLex;
33 :
34 : typedef struct ListParsedLex
35 : {
36 : ParsedLex *head;
37 : ParsedLex *tail;
38 : } ListParsedLex;
39 :
40 : typedef struct
41 : {
42 : TSConfigCacheEntry *cfg;
43 : Oid curDictId;
44 : int posDict;
45 : DictSubState dictState;
46 : ParsedLex *curSub;
47 : ListParsedLex towork; /* current list to work */
48 : ListParsedLex waste; /* list of lexemes that already lexized */
49 :
50 : /*
51 : * fields to store last variant to lexize (basically, thesaurus or similar
52 : * to, which wants several lexemes
53 : */
54 :
55 : ParsedLex *lastRes;
56 : TSLexeme *tmpRes;
57 : } LexizeData;
58 :
59 : static void
60 448 : LexizeInit(LexizeData *ld, TSConfigCacheEntry *cfg)
61 : {
62 448 : ld->cfg = cfg;
63 448 : ld->curDictId = InvalidOid;
64 448 : ld->posDict = 0;
65 448 : ld->towork.head = ld->towork.tail = ld->curSub = NULL;
66 448 : ld->waste.head = ld->waste.tail = NULL;
67 448 : ld->lastRes = NULL;
68 448 : ld->tmpRes = NULL;
69 448 : }
70 :
71 : static void
72 6134 : LPLAddTail(ListParsedLex *list, ParsedLex *newpl)
73 : {
74 6134 : if (list->tail)
75 : {
76 37 : list->tail->next = newpl;
77 37 : list->tail = newpl;
78 : }
79 : else
80 6097 : list->head = list->tail = newpl;
81 6134 : newpl->next = NULL;
82 6134 : }
83 :
84 : static ParsedLex *
85 3067 : LPLRemoveHead(ListParsedLex *list)
86 : {
87 3067 : ParsedLex *res = list->head;
88 :
89 3067 : if (list->head)
90 3067 : list->head = list->head->next;
91 :
92 3067 : if (list->head == NULL)
93 3046 : list->tail = NULL;
94 :
95 3067 : return res;
96 : }
97 :
98 : static void
99 3067 : LexizeAddLemm(LexizeData *ld, int type, char *lemm, int lenlemm)
100 : {
101 3067 : ParsedLex *newpl = (ParsedLex *) palloc(sizeof(ParsedLex));
102 :
103 3067 : newpl->type = type;
104 3067 : newpl->lemm = lemm;
105 3067 : newpl->lenlemm = lenlemm;
106 3067 : LPLAddTail(&ld->towork, newpl);
107 3067 : ld->curSub = ld->towork.tail;
108 3067 : }
109 :
110 : static void
111 3067 : RemoveHead(LexizeData *ld)
112 : {
113 3067 : LPLAddTail(&ld->waste, LPLRemoveHead(&ld->towork));
114 :
115 3067 : ld->posDict = 0;
116 3067 : }
117 :
118 : static void
119 4571 : setCorrLex(LexizeData *ld, ParsedLex **correspondLexem)
120 : {
121 4571 : if (correspondLexem)
122 : {
123 1600 : *correspondLexem = ld->waste.head;
124 : }
125 : else
126 : {
127 : ParsedLex *tmp,
128 2971 : *ptr = ld->waste.head;
129 :
130 7932 : while (ptr)
131 : {
132 1990 : tmp = ptr->next;
133 1990 : pfree(ptr);
134 1990 : ptr = tmp;
135 : }
136 : }
137 4571 : ld->waste.head = ld->waste.tail = NULL;
138 4571 : }
139 :
140 : static void
141 8 : moveToWaste(LexizeData *ld, ParsedLex *stop)
142 : {
143 8 : bool go = true;
144 :
145 38 : while (ld->towork.head && go)
146 : {
147 22 : if (ld->towork.head == stop)
148 : {
149 8 : ld->curSub = stop->next;
150 8 : go = false;
151 : }
152 22 : RemoveHead(ld);
153 : }
154 8 : }
155 :
156 : static void
157 8 : setNewTmpRes(LexizeData *ld, ParsedLex *lex, TSLexeme *res)
158 : {
159 8 : if (ld->tmpRes)
160 : {
161 : TSLexeme *ptr;
162 :
163 4 : for (ptr = ld->tmpRes; ptr->lexeme; ptr++)
164 2 : pfree(ptr->lexeme);
165 2 : pfree(ld->tmpRes);
166 : }
167 8 : ld->tmpRes = res;
168 8 : ld->lastRes = lex;
169 8 : }
170 :
171 : static TSLexeme *
172 4579 : LexizeExec(LexizeData *ld, ParsedLex **correspondLexem)
173 : {
174 : int i;
175 : ListDictionary *map;
176 : TSDictionaryCacheEntry *dict;
177 : TSLexeme *res;
178 :
179 4579 : if (ld->curDictId == InvalidOid)
180 : {
181 : /*
182 : * usual mode: dictionary wants only one word, but we should keep in
183 : * mind that we should go through all stack
184 : */
185 :
186 10649 : while (ld->towork.head)
187 : {
188 3053 : ParsedLex *curVal = ld->towork.head;
189 3053 : char *curValLemm = curVal->lemm;
190 3053 : int curValLenLemm = curVal->lenlemm;
191 :
192 3053 : map = ld->cfg->map + curVal->type;
193 :
194 3053 : if (curVal->type == 0 || curVal->type >= ld->cfg->lenmap || map->len == 0)
195 : {
196 : /* skip this type of lexeme */
197 1549 : RemoveHead(ld);
198 1549 : continue;
199 : }
200 :
201 1586 : for (i = ld->posDict; i < map->len; i++)
202 : {
203 1586 : dict = lookup_ts_dictionary_cache(map->dictIds[i]);
204 :
205 1586 : ld->dictState.isend = ld->dictState.getnext = false;
206 1586 : ld->dictState.private_state = NULL;
207 1586 : res = (TSLexeme *) DatumGetPointer(FunctionCall4(
208 : &(dict->lexize),
209 : PointerGetDatum(dict->dictData),
210 : PointerGetDatum(curValLemm),
211 : Int32GetDatum(curValLenLemm),
212 : PointerGetDatum(&ld->dictState)
213 : ));
214 :
215 1586 : if (ld->dictState.getnext)
216 : {
217 : /*
218 : * dictionary wants next word, so setup and store current
219 : * position and go to multiword mode
220 : */
221 :
222 8 : ld->curDictId = DatumGetObjectId(map->dictIds[i]);
223 8 : ld->posDict = i + 1;
224 8 : ld->curSub = curVal->next;
225 8 : if (res)
226 6 : setNewTmpRes(ld, curVal, res);
227 8 : return LexizeExec(ld, correspondLexem);
228 : }
229 :
230 1578 : if (!res) /* dictionary doesn't know this lexeme */
231 82 : continue;
232 :
233 1496 : if (res->flags & TSL_FILTER)
234 : {
235 0 : curValLemm = res->lexeme;
236 0 : curValLenLemm = strlen(res->lexeme);
237 0 : continue;
238 : }
239 :
240 1496 : RemoveHead(ld);
241 1496 : setCorrLex(ld, correspondLexem);
242 1496 : return res;
243 : }
244 :
245 0 : RemoveHead(ld);
246 : }
247 : }
248 : else
249 : { /* curDictId is valid */
250 29 : dict = lookup_ts_dictionary_cache(ld->curDictId);
251 :
252 : /*
253 : * Dictionary ld->curDictId asks us about following words
254 : */
255 :
256 29 : while (ld->curSub)
257 : {
258 21 : ParsedLex *curVal = ld->curSub;
259 :
260 21 : map = ld->cfg->map + curVal->type;
261 :
262 21 : if (curVal->type != 0)
263 : {
264 20 : bool dictExists = false;
265 :
266 20 : if (curVal->type >= ld->cfg->lenmap || map->len == 0)
267 : {
268 : /* skip this type of lexeme */
269 10 : ld->curSub = curVal->next;
270 10 : continue;
271 : }
272 :
273 : /*
274 : * We should be sure that current type of lexeme is recognized
275 : * by our dictionary: we just check is it exist in list of
276 : * dictionaries ?
277 : */
278 30 : for (i = 0; i < map->len && !dictExists; i++)
279 20 : if (ld->curDictId == DatumGetObjectId(map->dictIds[i]))
280 10 : dictExists = true;
281 :
282 10 : if (!dictExists)
283 : {
284 : /*
285 : * Dictionary can't work with current tpe of lexeme,
286 : * return to basic mode and redo all stored lexemes
287 : */
288 0 : ld->curDictId = InvalidOid;
289 0 : return LexizeExec(ld, correspondLexem);
290 : }
291 : }
292 :
293 11 : ld->dictState.isend = (curVal->type == 0) ? true : false;
294 11 : ld->dictState.getnext = false;
295 :
296 11 : res = (TSLexeme *) DatumGetPointer(FunctionCall4(
297 : &(dict->lexize),
298 : PointerGetDatum(dict->dictData),
299 : PointerGetDatum(curVal->lemm),
300 : Int32GetDatum(curVal->lenlemm),
301 : PointerGetDatum(&ld->dictState)
302 : ));
303 :
304 11 : if (ld->dictState.getnext)
305 : {
306 : /* Dictionary wants one more */
307 3 : ld->curSub = curVal->next;
308 3 : if (res)
309 2 : setNewTmpRes(ld, curVal, res);
310 3 : continue;
311 : }
312 :
313 8 : if (res || ld->tmpRes)
314 : {
315 : /*
316 : * Dictionary normalizes lexemes, so we remove from stack all
317 : * used lexemes, return to basic mode and redo end of stack
318 : * (if it exists)
319 : */
320 8 : if (res)
321 : {
322 4 : moveToWaste(ld, ld->curSub);
323 : }
324 : else
325 : {
326 4 : res = ld->tmpRes;
327 4 : moveToWaste(ld, ld->lastRes);
328 : }
329 :
330 : /* reset to initial state */
331 8 : ld->curDictId = InvalidOid;
332 8 : ld->posDict = 0;
333 8 : ld->lastRes = NULL;
334 8 : ld->tmpRes = NULL;
335 8 : setCorrLex(ld, correspondLexem);
336 8 : return res;
337 : }
338 :
339 : /*
340 : * Dict don't want next lexem and didn't recognize anything, redo
341 : * from ld->towork.head
342 : */
343 0 : ld->curDictId = InvalidOid;
344 0 : return LexizeExec(ld, correspondLexem);
345 : }
346 : }
347 :
348 3067 : setCorrLex(ld, correspondLexem);
349 3067 : return NULL;
350 : }
351 :
352 : /*
353 : * Parse string and lexize words.
354 : *
355 : * prs will be filled in.
356 : */
357 : void
358 397 : parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen)
359 : {
360 : int type,
361 : lenlemm;
362 397 : char *lemm = NULL;
363 : LexizeData ldata;
364 : TSLexeme *norms;
365 : TSConfigCacheEntry *cfg;
366 : TSParserCacheEntry *prsobj;
367 : void *prsdata;
368 :
369 397 : cfg = lookup_ts_config_cache(cfgId);
370 397 : prsobj = lookup_ts_parser_cache(cfg->prsId);
371 :
372 397 : prsdata = (void *) DatumGetPointer(FunctionCall2(&prsobj->prsstart,
373 : PointerGetDatum(buf),
374 : Int32GetDatum(buflen)));
375 :
376 397 : LexizeInit(&ldata, cfg);
377 :
378 : do
379 : {
380 1990 : type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken),
381 : PointerGetDatum(prsdata),
382 : PointerGetDatum(&lemm),
383 : PointerGetDatum(&lenlemm)));
384 :
385 1990 : if (type > 0 && lenlemm >= MAXSTRLEN)
386 : {
387 : #ifdef IGNORE_LONGLEXEME
388 0 : ereport(NOTICE,
389 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
390 : errmsg("word is too long to be indexed"),
391 : errdetail("Words longer than %d characters are ignored.",
392 : MAXSTRLEN)));
393 0 : continue;
394 : #else
395 : ereport(ERROR,
396 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
397 : errmsg("word is too long to be indexed"),
398 : errdetail("Words longer than %d characters are ignored.",
399 : MAXSTRLEN)));
400 : #endif
401 : }
402 :
403 1990 : LexizeAddLemm(&ldata, type, lemm, lenlemm);
404 :
405 4961 : while ((norms = LexizeExec(&ldata, NULL)) != NULL)
406 : {
407 981 : TSLexeme *ptr = norms;
408 :
409 981 : prs->pos++; /* set pos */
410 :
411 2800 : while (ptr->lexeme)
412 : {
413 838 : if (prs->curwords == prs->lenwords)
414 : {
415 40 : prs->lenwords *= 2;
416 40 : prs->words = (ParsedWord *) repalloc((void *) prs->words, prs->lenwords * sizeof(ParsedWord));
417 : }
418 :
419 838 : if (ptr->flags & TSL_ADDPOS)
420 4 : prs->pos++;
421 838 : prs->words[prs->curwords].len = strlen(ptr->lexeme);
422 838 : prs->words[prs->curwords].word = ptr->lexeme;
423 838 : prs->words[prs->curwords].nvariant = ptr->nvariant;
424 838 : prs->words[prs->curwords].flags = ptr->flags & TSL_PREFIX;
425 838 : prs->words[prs->curwords].alen = 0;
426 838 : prs->words[prs->curwords].pos.pos = LIMITPOS(prs->pos);
427 838 : ptr++;
428 838 : prs->curwords++;
429 : }
430 981 : pfree(norms);
431 : }
432 1990 : } while (type > 0);
433 :
434 397 : FunctionCall1(&(prsobj->prsend), PointerGetDatum(prsdata));
435 397 : }
436 :
437 : /*
438 : * Headline framework
439 : */
440 : static void
441 1026 : hladdword(HeadlineParsedText *prs, char *buf, int buflen, int type)
442 : {
443 2059 : while (prs->curwords >= prs->lenwords)
444 : {
445 7 : prs->lenwords *= 2;
446 7 : prs->words = (HeadlineWordEntry *) repalloc((void *) prs->words, prs->lenwords * sizeof(HeadlineWordEntry));
447 : }
448 1026 : memset(&(prs->words[prs->curwords]), 0, sizeof(HeadlineWordEntry));
449 1026 : prs->words[prs->curwords].type = (uint8) type;
450 1026 : prs->words[prs->curwords].len = buflen;
451 1026 : prs->words[prs->curwords].word = palloc(buflen);
452 1026 : memcpy(prs->words[prs->curwords].word, buf, buflen);
453 1026 : prs->curwords++;
454 1026 : }
455 :
456 : static void
457 343 : hlfinditem(HeadlineParsedText *prs, TSQuery query, int32 pos, char *buf, int buflen)
458 : {
459 : int i;
460 343 : QueryItem *item = GETQUERY(query);
461 : HeadlineWordEntry *word;
462 :
463 698 : while (prs->curwords + query->size >= prs->lenwords)
464 : {
465 12 : prs->lenwords *= 2;
466 12 : prs->words = (HeadlineWordEntry *) repalloc((void *) prs->words, prs->lenwords * sizeof(HeadlineWordEntry));
467 : }
468 :
469 343 : word = &(prs->words[prs->curwords - 1]);
470 343 : word->pos = LIMITPOS(pos);
471 1540 : for (i = 0; i < query->size; i++)
472 : {
473 1967 : if (item->type == QI_VAL &&
474 770 : tsCompareString(GETOPERAND(query) + item->qoperand.distance, item->qoperand.length,
475 770 : buf, buflen, item->qoperand.prefix) == 0)
476 : {
477 64 : if (word->item)
478 : {
479 0 : memcpy(&(prs->words[prs->curwords]), word, sizeof(HeadlineWordEntry));
480 0 : prs->words[prs->curwords].item = &item->qoperand;
481 0 : prs->words[prs->curwords].repeated = 1;
482 0 : prs->curwords++;
483 : }
484 : else
485 64 : word->item = &item->qoperand;
486 : }
487 1197 : item++;
488 : }
489 343 : }
490 :
491 : static void
492 1600 : addHLParsedLex(HeadlineParsedText *prs, TSQuery query, ParsedLex *lexs, TSLexeme *norms)
493 : {
494 : ParsedLex *tmplexs;
495 : TSLexeme *ptr;
496 : int32 savedpos;
497 :
498 4277 : while (lexs)
499 : {
500 1077 : if (lexs->type > 0)
501 1026 : hladdword(prs, lexs->lemm, lexs->lenlemm, lexs->type);
502 :
503 1077 : ptr = norms;
504 1077 : savedpos = prs->vectorpos;
505 2497 : while (ptr && ptr->lexeme)
506 : {
507 343 : if (ptr->flags & TSL_ADDPOS)
508 0 : savedpos++;
509 343 : hlfinditem(prs, query, savedpos, ptr->lexeme, strlen(ptr->lexeme));
510 343 : ptr++;
511 : }
512 :
513 1077 : tmplexs = lexs->next;
514 1077 : pfree(lexs);
515 1077 : lexs = tmplexs;
516 : }
517 :
518 1600 : if (norms)
519 : {
520 523 : ptr = norms;
521 1389 : while (ptr->lexeme)
522 : {
523 343 : if (ptr->flags & TSL_ADDPOS)
524 0 : prs->vectorpos++;
525 343 : pfree(ptr->lexeme);
526 343 : ptr++;
527 : }
528 523 : pfree(norms);
529 : }
530 1600 : }
531 :
532 : void
533 51 : hlparsetext(Oid cfgId, HeadlineParsedText *prs, TSQuery query, char *buf, int buflen)
534 : {
535 : int type,
536 : lenlemm;
537 51 : char *lemm = NULL;
538 : LexizeData ldata;
539 : TSLexeme *norms;
540 : ParsedLex *lexs;
541 : TSConfigCacheEntry *cfg;
542 : TSParserCacheEntry *prsobj;
543 : void *prsdata;
544 :
545 51 : cfg = lookup_ts_config_cache(cfgId);
546 51 : prsobj = lookup_ts_parser_cache(cfg->prsId);
547 :
548 51 : prsdata = (void *) DatumGetPointer(FunctionCall2(&(prsobj->prsstart),
549 : PointerGetDatum(buf),
550 : Int32GetDatum(buflen)));
551 :
552 51 : LexizeInit(&ldata, cfg);
553 :
554 : do
555 : {
556 1077 : type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken),
557 : PointerGetDatum(prsdata),
558 : PointerGetDatum(&lemm),
559 : PointerGetDatum(&lenlemm)));
560 :
561 1077 : if (type > 0 && lenlemm >= MAXSTRLEN)
562 : {
563 : #ifdef IGNORE_LONGLEXEME
564 0 : ereport(NOTICE,
565 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
566 : errmsg("word is too long to be indexed"),
567 : errdetail("Words longer than %d characters are ignored.",
568 : MAXSTRLEN)));
569 0 : continue;
570 : #else
571 : ereport(ERROR,
572 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
573 : errmsg("word is too long to be indexed"),
574 : errdetail("Words longer than %d characters are ignored.",
575 : MAXSTRLEN)));
576 : #endif
577 : }
578 :
579 1077 : LexizeAddLemm(&ldata, type, lemm, lenlemm);
580 :
581 : do
582 : {
583 1600 : if ((norms = LexizeExec(&ldata, &lexs)) != NULL)
584 : {
585 523 : prs->vectorpos++;
586 523 : addHLParsedLex(prs, query, lexs, norms);
587 : }
588 : else
589 1077 : addHLParsedLex(prs, query, lexs, NULL);
590 1600 : } while (norms);
591 :
592 1077 : } while (type > 0);
593 :
594 51 : FunctionCall1(&(prsobj->prsend), PointerGetDatum(prsdata));
595 51 : }
596 :
597 : text *
598 51 : generateHeadline(HeadlineParsedText *prs)
599 : {
600 : text *out;
601 : char *ptr;
602 51 : int len = 128;
603 51 : int numfragments = 0;
604 51 : int16 infrag = 0;
605 :
606 51 : HeadlineWordEntry *wrd = prs->words;
607 :
608 51 : out = (text *) palloc(len);
609 51 : ptr = ((char *) out) + VARHDRSZ;
610 :
611 1128 : while (wrd - prs->words < prs->curwords)
612 : {
613 2062 : while (wrd->len + prs->stopsellen + prs->startsellen + prs->fragdelimlen + (ptr - ((char *) out)) >= len)
614 : {
615 10 : int dist = ptr - ((char *) out);
616 :
617 10 : len *= 2;
618 10 : out = (text *) repalloc(out, len);
619 10 : ptr = ((char *) out) + dist;
620 : }
621 :
622 1026 : if (wrd->in && !wrd->repeated)
623 : {
624 592 : if (!infrag)
625 : {
626 :
627 : /* start of a new fragment */
628 53 : infrag = 1;
629 53 : numfragments++;
630 : /* add a fragment delimiter if this is after the first one */
631 53 : if (numfragments > 1)
632 : {
633 2 : memcpy(ptr, prs->fragdelim, prs->fragdelimlen);
634 2 : ptr += prs->fragdelimlen;
635 : }
636 :
637 : }
638 1184 : if (wrd->replace)
639 : {
640 0 : *ptr = ' ';
641 0 : ptr++;
642 : }
643 592 : else if (!wrd->skip)
644 : {
645 591 : if (wrd->selected)
646 : {
647 54 : memcpy(ptr, prs->startsel, prs->startsellen);
648 54 : ptr += prs->startsellen;
649 : }
650 591 : memcpy(ptr, wrd->word, wrd->len);
651 591 : ptr += wrd->len;
652 591 : if (wrd->selected)
653 : {
654 54 : memcpy(ptr, prs->stopsel, prs->stopsellen);
655 54 : ptr += prs->stopsellen;
656 : }
657 : }
658 : }
659 434 : else if (!wrd->repeated)
660 : {
661 434 : if (infrag)
662 12 : infrag = 0;
663 434 : pfree(wrd->word);
664 : }
665 :
666 1026 : wrd++;
667 : }
668 :
669 51 : SET_VARSIZE(out, ptr - ((char *) out));
670 51 : return out;
671 : }
|