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