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