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