TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * tsvector.c
4 : * I/O functions for tsvector
5 : *
6 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
7 : *
8 : *
9 : * IDENTIFICATION
10 : * src/backend/utils/adt/tsvector.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 :
15 : #include "postgres.h"
16 :
17 : #include "libpq/pqformat.h"
18 : #include "nodes/miscnodes.h"
19 : #include "tsearch/ts_locale.h"
20 : #include "tsearch/ts_utils.h"
21 : #include "utils/builtins.h"
22 : #include "utils/memutils.h"
23 : #include "varatt.h"
24 :
25 : typedef struct
26 : {
27 : WordEntry entry; /* must be first! */
28 : WordEntryPos *pos;
29 : int poslen; /* number of elements in pos */
30 : } WordEntryIN;
31 :
32 :
33 : /* Compare two WordEntryPos values for qsort */
34 : int
35 GIC 504 : compareWordEntryPos(const void *a, const void *b)
36 : {
37 CBC 504 : int apos = WEP_GETPOS(*(const WordEntryPos *) a);
38 GIC 504 : int bpos = WEP_GETPOS(*(const WordEntryPos *) b);
39 ECB :
40 CBC 504 : if (apos == bpos)
41 GIC 12 : return 0;
42 CBC 492 : return (apos > bpos) ? 1 : -1;
43 ECB : }
44 :
45 : /*
46 : * Removes duplicate pos entries. If there's two entries with same pos but
47 : * different weight, the higher weight is retained, so we can't use
48 : * qunique here.
49 : *
50 : * Returns new length.
51 : */
52 : static int
53 GIC 4803 : uniquePos(WordEntryPos *a, int l)
54 : {
55 ECB : WordEntryPos *ptr,
56 : *res;
57 :
58 GIC 4803 : if (l <= 1)
59 4536 : return l;
60 ECB :
61 GNC 267 : qsort(a, l, sizeof(WordEntryPos), compareWordEntryPos);
62 :
63 CBC 267 : res = a;
64 GIC 267 : ptr = a + 1;
65 CBC 726 : while (ptr - a < l)
66 ECB : {
67 CBC 459 : if (WEP_GETPOS(*ptr) != WEP_GETPOS(*res))
68 : {
69 447 : res++;
70 GIC 447 : *res = *ptr;
71 CBC 447 : if (res - a >= MAXNUMPOS - 1 ||
72 447 : WEP_GETPOS(*res) == MAXENTRYPOS - 1)
73 ECB : break;
74 : }
75 GIC 12 : else if (WEP_GETWEIGHT(*ptr) > WEP_GETWEIGHT(*res))
76 3 : WEP_SETWEIGHT(*res, WEP_GETWEIGHT(*ptr));
77 CBC 459 : ptr++;
78 ECB : }
79 :
80 GIC 267 : return res + 1 - a;
81 : }
82 ECB :
83 : /* Compare two WordEntryIN values for qsort */
84 : static int
85 GIC 536253 : compareentry(const void *va, const void *vb, void *arg)
86 : {
87 CBC 536253 : const WordEntryIN *a = (const WordEntryIN *) va;
88 GIC 536253 : const WordEntryIN *b = (const WordEntryIN *) vb;
89 CBC 536253 : char *BufferStr = (char *) arg;
90 ECB :
91 CBC 1072506 : return tsCompareString(&BufferStr[a->entry.pos], a->entry.len,
92 GIC 536253 : &BufferStr[b->entry.pos], b->entry.len,
93 ECB : false);
94 : }
95 :
96 : /*
97 : * Sort an array of WordEntryIN, remove duplicates.
98 : * *outbuflen receives the amount of space needed for strings and positions.
99 : */
100 : static int
101 GIC 1853 : uniqueentry(WordEntryIN *a, int l, char *buf, int *outbuflen)
102 : {
103 ECB : int buflen;
104 : WordEntryIN *ptr,
105 : *res;
106 :
107 GIC 1853 : Assert(l >= 1);
108 :
109 CBC 1853 : if (l > 1)
110 GNC 1802 : qsort_arg(a, l, sizeof(WordEntryIN), compareentry, buf);
111 ECB :
112 GIC 1853 : buflen = 0;
113 CBC 1853 : res = a;
114 1853 : ptr = a + 1;
115 90414 : while (ptr - a < l)
116 ECB : {
117 GIC 88561 : if (!(ptr->entry.len == res->entry.len &&
118 CBC 88027 : strncmp(&buf[ptr->entry.pos], &buf[res->entry.pos],
119 88027 : res->entry.len) == 0))
120 ECB : {
121 : /* done accumulating data into *res, count space needed */
122 GIC 85798 : buflen += res->entry.len;
123 CBC 85798 : if (res->entry.haspos)
124 ECB : {
125 GIC 4497 : res->poslen = uniquePos(res->pos, res->poslen);
126 CBC 4497 : buflen = SHORTALIGN(buflen);
127 4497 : buflen += res->poslen * sizeof(WordEntryPos) + sizeof(uint16);
128 ECB : }
129 GIC 85798 : res++;
130 CBC 85798 : if (res != ptr)
131 44286 : memcpy(res, ptr, sizeof(WordEntryIN));
132 ECB : }
133 GIC 2763 : else if (ptr->entry.haspos)
134 ECB : {
135 GIC 159 : if (res->entry.haspos)
136 ECB : {
137 : /* append ptr's positions to res's positions */
138 GIC 156 : int newlen = ptr->poslen + res->poslen;
139 ECB :
140 GIC 156 : res->pos = (WordEntryPos *)
141 CBC 156 : repalloc(res->pos, newlen * sizeof(WordEntryPos));
142 156 : memcpy(&res->pos[res->poslen], ptr->pos,
143 156 : ptr->poslen * sizeof(WordEntryPos));
144 156 : res->poslen = newlen;
145 156 : pfree(ptr->pos);
146 ECB : }
147 : else
148 : {
149 : /* just give ptr's positions to pos */
150 GIC 3 : res->entry.haspos = 1;
151 CBC 3 : res->pos = ptr->pos;
152 3 : res->poslen = ptr->poslen;
153 ECB : }
154 : }
155 GIC 88561 : ptr++;
156 ECB : }
157 :
158 : /* count space needed for last item */
159 GIC 1853 : buflen += res->entry.len;
160 CBC 1853 : if (res->entry.haspos)
161 ECB : {
162 GIC 306 : res->poslen = uniquePos(res->pos, res->poslen);
163 CBC 306 : buflen = SHORTALIGN(buflen);
164 306 : buflen += res->poslen * sizeof(WordEntryPos) + sizeof(uint16);
165 ECB : }
166 :
167 GIC 1853 : *outbuflen = buflen;
168 CBC 1853 : return res + 1 - a;
169 ECB : }
170 :
171 : static int
172 UIC 0 : WordEntryCMP(WordEntry *a, WordEntry *b, char *buf)
173 EUB : {
174 UIC 0 : return compareentry(a, b, buf);
175 EUB : }
176 :
177 :
178 : Datum
179 GIC 1886 : tsvectorin(PG_FUNCTION_ARGS)
180 ECB : {
181 GIC 1886 : char *buf = PG_GETARG_CSTRING(0);
182 GNC 1886 : Node *escontext = fcinfo->context;
183 ECB : TSVectorParseState state;
184 : WordEntryIN *arr;
185 : int totallen;
186 : int arrlen; /* allocated size of arr */
187 : WordEntry *inarr;
188 GIC 1886 : int len = 0;
189 : TSVector in;
190 ECB : int i;
191 : char *token;
192 : int toklen;
193 : WordEntryPos *pos;
194 : int poslen;
195 : char *strbuf;
196 : int stroff;
197 :
198 : /*
199 : * Tokens are appended to tmpbuf, cur is a pointer to the end of used
200 : * space in tmpbuf.
201 : */
202 : char *tmpbuf;
203 : char *cur;
204 GIC 1886 : int buflen = 256; /* allocated size of tmpbuf */
205 :
206 GNC 1886 : state = init_tsvector_parser(buf, 0, escontext);
207 :
208 CBC 1886 : arrlen = 64;
209 GIC 1886 : arr = (WordEntryIN *) palloc(sizeof(WordEntryIN) * arrlen);
210 CBC 1886 : cur = tmpbuf = (char *) palloc(buflen);
211 ECB :
212 CBC 92300 : while (gettoken_tsvector(state, &token, &toklen, &pos, &poslen, NULL))
213 : {
214 90414 : if (toklen >= MAXSTRLEN)
215 UNC 0 : ereturn(escontext, (Datum) 0,
216 ECB : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
217 EUB : errmsg("word is too long (%ld bytes, max %ld bytes)",
218 : (long) toklen,
219 : (long) (MAXSTRLEN - 1))));
220 :
221 GIC 90414 : if (cur - tmpbuf > MAXSTRPOS)
222 UNC 0 : ereturn(escontext, (Datum) 0,
223 ECB : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
224 EUB : errmsg("string is too long for tsvector (%ld bytes, max %ld bytes)",
225 : (long) (cur - tmpbuf), (long) MAXSTRPOS)));
226 :
227 : /*
228 : * Enlarge buffers if needed
229 : */
230 GIC 90414 : if (len >= arrlen)
231 : {
232 CBC 657 : arrlen *= 2;
233 : arr = (WordEntryIN *)
234 GNC 657 : repalloc(arr, sizeof(WordEntryIN) * arrlen);
235 : }
236 CBC 90414 : while ((cur - tmpbuf) + toklen >= buflen)
237 : {
238 LBC 0 : int dist = cur - tmpbuf;
239 :
240 UBC 0 : buflen *= 2;
241 UNC 0 : tmpbuf = (char *) repalloc(tmpbuf, buflen);
242 UBC 0 : cur = tmpbuf + dist;
243 EUB : }
244 GBC 90414 : arr[len].entry.len = toklen;
245 GIC 90414 : arr[len].entry.pos = cur - tmpbuf;
246 GNC 90414 : memcpy(cur, token, toklen);
247 CBC 90414 : cur += toklen;
248 ECB :
249 CBC 90414 : if (poslen != 0)
250 : {
251 4959 : arr[len].entry.haspos = 1;
252 GIC 4959 : arr[len].pos = pos;
253 CBC 4959 : arr[len].poslen = poslen;
254 ECB : }
255 : else
256 : {
257 GIC 85455 : arr[len].entry.haspos = 0;
258 85455 : arr[len].pos = NULL;
259 CBC 85455 : arr[len].poslen = 0;
260 ECB : }
261 CBC 90414 : len++;
262 : }
263 ECB :
264 GIC 1883 : close_tsvector_parser(state);
265 :
266 : /* Did gettoken_tsvector fail? */
267 GNC 1883 : if (SOFT_ERROR_OCCURRED(escontext))
268 6 : PG_RETURN_NULL();
269 :
270 CBC 1877 : if (len > 0)
271 GIC 1853 : len = uniqueentry(arr, len, tmpbuf, &buflen);
272 : else
273 CBC 24 : buflen = 0;
274 ECB :
275 GIC 1877 : if (buflen > MAXSTRPOS)
276 UNC 0 : ereturn(escontext, (Datum) 0,
277 ECB : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
278 : errmsg("string is too long for tsvector (%d bytes, max %d bytes)", buflen, MAXSTRPOS)));
279 :
280 GIC 1877 : totallen = CALCDATASIZE(len, buflen);
281 CBC 1877 : in = (TSVector) palloc0(totallen);
282 GBC 1877 : SET_VARSIZE(in, totallen);
283 GIC 1877 : in->size = len;
284 1877 : inarr = ARRPTR(in);
285 1877 : strbuf = STRPTR(in);
286 CBC 1877 : stroff = 0;
287 89528 : for (i = 0; i < len; i++)
288 ECB : {
289 CBC 87651 : memcpy(strbuf + stroff, &tmpbuf[arr[i].entry.pos], arr[i].entry.len);
290 87651 : arr[i].entry.pos = stroff;
291 87651 : stroff += arr[i].entry.len;
292 87651 : if (arr[i].entry.haspos)
293 ECB : {
294 : /* This should be unreachable because of MAXNUMPOS restrictions */
295 GIC 4803 : if (arr[i].poslen > 0xFFFF)
296 LBC 0 : elog(ERROR, "positions array too long");
297 ECB :
298 : /* Copy number of positions */
299 CBC 4803 : stroff = SHORTALIGN(stroff);
300 GIC 4803 : *(uint16 *) (strbuf + stroff) = (uint16) arr[i].poslen;
301 4803 : stroff += sizeof(uint16);
302 ECB :
303 EUB : /* Copy positions */
304 GIC 4803 : memcpy(strbuf + stroff, arr[i].pos, arr[i].poslen * sizeof(WordEntryPos));
305 4803 : stroff += arr[i].poslen * sizeof(WordEntryPos);
306 ECB :
307 CBC 4803 : pfree(arr[i].pos);
308 ECB : }
309 GIC 87651 : inarr[i] = arr[i].entry;
310 : }
311 ECB :
312 CBC 1877 : Assert((strbuf + stroff - (char *) in) == totallen);
313 :
314 1877 : PG_RETURN_TSVECTOR(in);
315 : }
316 ECB :
317 : Datum
318 GIC 2378 : tsvectorout(PG_FUNCTION_ARGS)
319 ECB : {
320 GIC 2378 : TSVector out = PG_GETARG_TSVECTOR(0);
321 ECB : char *outbuf;
322 : int32 i,
323 GIC 2378 : lenbuf = 0,
324 : pp;
325 CBC 2378 : WordEntry *ptr = ARRPTR(out);
326 : char *curbegin,
327 ECB : *curin,
328 : *curout;
329 :
330 CBC 2378 : lenbuf = out->size * 2 /* '' */ + out->size - 1 /* space */ + 2 /* \0 */ ;
331 GIC 118939 : for (i = 0; i < out->size; i++)
332 ECB : {
333 GIC 116561 : lenbuf += ptr[i].len * 2 * pg_database_encoding_max_length() /* for escape */ ;
334 116561 : if (ptr[i].haspos)
335 6559 : lenbuf += 1 /* : */ + 7 /* int2 + , + weight */ * POSDATALEN(out, &(ptr[i]));
336 : }
337 ECB :
338 CBC 2378 : curout = outbuf = (char *) palloc(lenbuf);
339 GIC 118939 : for (i = 0; i < out->size; i++)
340 ECB : {
341 CBC 116561 : curbegin = curin = STRPTR(out) + ptr->pos;
342 116561 : if (i != 0)
343 GIC 114276 : *curout++ = ' ';
344 116561 : *curout++ = '\'';
345 CBC 353066 : while (curin - curbegin < ptr->len)
346 ECB : {
347 GIC 236505 : int len = pg_mblen(curin);
348 ECB :
349 CBC 236505 : if (t_iseq(curin, '\''))
350 13 : *curout++ = '\'';
351 236492 : else if (t_iseq(curin, '\\'))
352 45 : *curout++ = '\\';
353 :
354 473010 : while (len--)
355 GIC 236505 : *curout++ = *curin++;
356 ECB : }
357 :
358 CBC 116561 : *curout++ = '\'';
359 116561 : if ((pp = POSDATALEN(out, ptr)) != 0)
360 : {
361 ECB : WordEntryPos *wptr;
362 :
363 GIC 6559 : *curout++ = ':';
364 6559 : wptr = POSDATAPTR(out, ptr);
365 CBC 13584 : while (pp)
366 ECB : {
367 GIC 7025 : curout += sprintf(curout, "%d", WEP_GETPOS(*wptr));
368 7025 : switch (WEP_GETWEIGHT(*wptr))
369 : {
370 CBC 58 : case 3:
371 58 : *curout++ = 'A';
372 58 : break;
373 GIC 34 : case 2:
374 CBC 34 : *curout++ = 'B';
375 34 : break;
376 GIC 115 : case 1:
377 CBC 115 : *curout++ = 'C';
378 115 : break;
379 6818 : case 0:
380 ECB : default:
381 CBC 6818 : break;
382 ECB : }
383 :
384 CBC 7025 : if (pp > 1)
385 466 : *curout++ = ',';
386 7025 : pp--;
387 GIC 7025 : wptr++;
388 ECB : }
389 : }
390 GIC 116561 : ptr++;
391 ECB : }
392 :
393 CBC 2378 : *curout = '\0';
394 2378 : PG_FREE_IF_COPY(out, 0);
395 GIC 2378 : PG_RETURN_CSTRING(outbuf);
396 : }
397 ECB :
398 : /*
399 : * Binary Input / Output functions. The binary format is as follows:
400 : *
401 : * uint32 number of lexemes
402 : *
403 : * for each lexeme:
404 : * lexeme text in client encoding, null-terminated
405 : * uint16 number of positions
406 : * for each position:
407 : * uint16 WordEntryPos
408 : */
409 :
410 : Datum
411 UIC 0 : tsvectorsend(PG_FUNCTION_ARGS)
412 : {
413 0 : TSVector vec = PG_GETARG_TSVECTOR(0);
414 : StringInfoData buf;
415 : int i,
416 : j;
417 0 : WordEntry *weptr = ARRPTR(vec);
418 EUB :
419 UIC 0 : pq_begintypsend(&buf);
420 EUB :
421 UIC 0 : pq_sendint32(&buf, vec->size);
422 0 : for (i = 0; i < vec->size; i++)
423 : {
424 EUB : uint16 npos;
425 :
426 : /*
427 : * the strings in the TSVector array are not null-terminated, so we
428 : * have to send the null-terminator separately
429 : */
430 UIC 0 : pq_sendtext(&buf, STRPTR(vec) + weptr->pos, weptr->len);
431 0 : pq_sendbyte(&buf, '\0');
432 :
433 0 : npos = POSDATALEN(vec, weptr);
434 0 : pq_sendint16(&buf, npos);
435 :
436 0 : if (npos > 0)
437 EUB : {
438 UBC 0 : WordEntryPos *wepptr = POSDATAPTR(vec, weptr);
439 :
440 0 : for (j = 0; j < npos; j++)
441 0 : pq_sendint16(&buf, wepptr[j]);
442 : }
443 0 : weptr++;
444 : }
445 EUB :
446 UIC 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
447 EUB : }
448 :
449 : Datum
450 UBC 0 : tsvectorrecv(PG_FUNCTION_ARGS)
451 : {
452 UIC 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
453 EUB : TSVector vec;
454 : int i;
455 : int32 nentries;
456 : int datalen; /* number of bytes used in the variable size
457 : * area after fixed size TSVector header and
458 : * WordEntries */
459 : Size hdrlen;
460 : Size len; /* allocated size of vec */
461 UIC 0 : bool needSort = false;
462 :
463 0 : nentries = pq_getmsgint(buf, sizeof(int32));
464 0 : if (nentries < 0 || nentries > (MaxAllocSize / sizeof(WordEntry)))
465 0 : elog(ERROR, "invalid size of tsvector");
466 :
467 0 : hdrlen = DATAHDRSIZE + sizeof(WordEntry) * nentries;
468 EUB :
469 UIC 0 : len = hdrlen * 2; /* times two to make room for lexemes */
470 UBC 0 : vec = (TSVector) palloc0(len);
471 0 : vec->size = nentries;
472 EUB :
473 UIC 0 : datalen = 0;
474 UBC 0 : for (i = 0; i < nentries; i++)
475 : {
476 EUB : const char *lexeme;
477 : uint16 npos;
478 : size_t lex_len;
479 :
480 UBC 0 : lexeme = pq_getmsgstring(buf);
481 0 : npos = (uint16) pq_getmsgint(buf, sizeof(uint16));
482 :
483 : /* sanity checks */
484 :
485 UIC 0 : lex_len = strlen(lexeme);
486 0 : if (lex_len > MAXSTRLEN)
487 UBC 0 : elog(ERROR, "invalid tsvector: lexeme too long");
488 EUB :
489 UIC 0 : if (datalen > MAXSTRPOS)
490 0 : elog(ERROR, "invalid tsvector: maximum total lexeme length exceeded");
491 :
492 UBC 0 : if (npos > MAXNUMPOS)
493 0 : elog(ERROR, "unexpected number of tsvector positions");
494 EUB :
495 : /*
496 : * Looks valid. Fill the WordEntry struct, and copy lexeme.
497 : *
498 : * But make sure the buffer is large enough first.
499 : */
500 UBC 0 : while (hdrlen + SHORTALIGN(datalen + lex_len) +
501 UIC 0 : (npos + 1) * sizeof(WordEntryPos) >= len)
502 : {
503 0 : len *= 2;
504 0 : vec = (TSVector) repalloc(vec, len);
505 : }
506 :
507 UBC 0 : vec->entries[i].haspos = (npos > 0) ? 1 : 0;
508 0 : vec->entries[i].len = lex_len;
509 UIC 0 : vec->entries[i].pos = datalen;
510 EUB :
511 UBC 0 : memcpy(STRPTR(vec) + datalen, lexeme, lex_len);
512 :
513 UIC 0 : datalen += lex_len;
514 EUB :
515 UBC 0 : if (i > 0 && WordEntryCMP(&vec->entries[i],
516 0 : &vec->entries[i - 1],
517 UIC 0 : STRPTR(vec)) <= 0)
518 UBC 0 : needSort = true;
519 :
520 EUB : /* Receive positions */
521 UIC 0 : if (npos > 0)
522 EUB : {
523 : uint16 j;
524 : WordEntryPos *wepptr;
525 :
526 : /*
527 : * Pad to 2-byte alignment if necessary. Though we used palloc0
528 : * for the initial allocation, subsequent repalloc'd memory areas
529 : * are not initialized to zero.
530 : */
531 UIC 0 : if (datalen != SHORTALIGN(datalen))
532 : {
533 0 : *(STRPTR(vec) + datalen) = '\0';
534 0 : datalen = SHORTALIGN(datalen);
535 : }
536 :
537 0 : memcpy(STRPTR(vec) + datalen, &npos, sizeof(uint16));
538 EUB :
539 UIC 0 : wepptr = POSDATAPTR(vec, &vec->entries[i]);
540 UBC 0 : for (j = 0; j < npos; j++)
541 EUB : {
542 UIC 0 : wepptr[j] = (WordEntryPos) pq_getmsgint(buf, sizeof(WordEntryPos));
543 0 : if (j > 0 && WEP_GETPOS(wepptr[j]) <= WEP_GETPOS(wepptr[j - 1]))
544 UBC 0 : elog(ERROR, "position information is misordered");
545 : }
546 EUB :
547 UBC 0 : datalen += (npos + 1) * sizeof(WordEntry);
548 : }
549 EUB : }
550 :
551 UBC 0 : SET_VARSIZE(vec, hdrlen + datalen);
552 :
553 UIC 0 : if (needSort)
554 UNC 0 : qsort_arg(ARRPTR(vec), vec->size, sizeof(WordEntry),
555 0 : compareentry, STRPTR(vec));
556 :
557 UIC 0 : PG_RETURN_TSVECTOR(vec);
558 EUB : }
|