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 1008 : compareWordEntryPos(const void *a, const void *b)
36 : {
37 1008 : int apos = WEP_GETPOS(*(const WordEntryPos *) a);
38 1008 : int bpos = WEP_GETPOS(*(const WordEntryPos *) b);
39 :
40 1008 : if (apos == bpos)
41 24 : return 0;
42 984 : return (apos > bpos) ? 1 : -1;
43 : }
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 9606 : uniquePos(WordEntryPos *a, int l)
54 : {
55 : WordEntryPos *ptr,
56 : *res;
57 :
58 9606 : if (l <= 1)
59 9072 : return l;
60 :
61 534 : qsort(a, l, sizeof(WordEntryPos), compareWordEntryPos);
62 :
63 534 : res = a;
64 534 : ptr = a + 1;
65 1452 : while (ptr - a < l)
66 : {
67 918 : if (WEP_GETPOS(*ptr) != WEP_GETPOS(*res))
68 : {
69 894 : res++;
70 894 : *res = *ptr;
71 894 : if (res - a >= MAXNUMPOS - 1 ||
72 894 : WEP_GETPOS(*res) == MAXENTRYPOS - 1)
73 : break;
74 : }
75 24 : else if (WEP_GETWEIGHT(*ptr) > WEP_GETWEIGHT(*res))
76 6 : WEP_SETWEIGHT(*res, WEP_GETWEIGHT(*ptr));
77 918 : ptr++;
78 : }
79 :
80 534 : return res + 1 - a;
81 : }
82 :
83 : /* Compare two WordEntryIN values for qsort */
84 : static int
85 1072506 : compareentry(const void *va, const void *vb, void *arg)
86 : {
87 1072506 : const WordEntryIN *a = (const WordEntryIN *) va;
88 1072506 : const WordEntryIN *b = (const WordEntryIN *) vb;
89 1072506 : char *BufferStr = (char *) arg;
90 :
91 2145012 : return tsCompareString(&BufferStr[a->entry.pos], a->entry.len,
92 1072506 : &BufferStr[b->entry.pos], b->entry.len,
93 : 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 3706 : uniqueentry(WordEntryIN *a, int l, char *buf, int *outbuflen)
102 : {
103 : int buflen;
104 : WordEntryIN *ptr,
105 : *res;
106 :
107 : Assert(l >= 1);
108 :
109 3706 : if (l > 1)
110 3604 : qsort_arg(a, l, sizeof(WordEntryIN), compareentry, buf);
111 :
112 3706 : buflen = 0;
113 3706 : res = a;
114 3706 : ptr = a + 1;
115 180828 : while (ptr - a < l)
116 : {
117 177122 : if (!(ptr->entry.len == res->entry.len &&
118 176054 : strncmp(&buf[ptr->entry.pos], &buf[res->entry.pos],
119 176054 : res->entry.len) == 0))
120 : {
121 : /* done accumulating data into *res, count space needed */
122 171596 : buflen += res->entry.len;
123 171596 : if (res->entry.haspos)
124 : {
125 8994 : res->poslen = uniquePos(res->pos, res->poslen);
126 8994 : buflen = SHORTALIGN(buflen);
127 8994 : buflen += res->poslen * sizeof(WordEntryPos) + sizeof(uint16);
128 : }
129 171596 : res++;
130 171596 : if (res != ptr)
131 88572 : memcpy(res, ptr, sizeof(WordEntryIN));
132 : }
133 5526 : else if (ptr->entry.haspos)
134 : {
135 318 : if (res->entry.haspos)
136 : {
137 : /* append ptr's positions to res's positions */
138 312 : int newlen = ptr->poslen + res->poslen;
139 :
140 312 : res->pos = (WordEntryPos *)
141 312 : repalloc(res->pos, newlen * sizeof(WordEntryPos));
142 312 : memcpy(&res->pos[res->poslen], ptr->pos,
143 312 : ptr->poslen * sizeof(WordEntryPos));
144 312 : res->poslen = newlen;
145 312 : pfree(ptr->pos);
146 : }
147 : else
148 : {
149 : /* just give ptr's positions to pos */
150 6 : res->entry.haspos = 1;
151 6 : res->pos = ptr->pos;
152 6 : res->poslen = ptr->poslen;
153 : }
154 : }
155 177122 : ptr++;
156 : }
157 :
158 : /* count space needed for last item */
159 3706 : buflen += res->entry.len;
160 3706 : if (res->entry.haspos)
161 : {
162 612 : res->poslen = uniquePos(res->pos, res->poslen);
163 612 : buflen = SHORTALIGN(buflen);
164 612 : buflen += res->poslen * sizeof(WordEntryPos) + sizeof(uint16);
165 : }
166 :
167 3706 : *outbuflen = buflen;
168 3706 : return res + 1 - a;
169 : }
170 :
171 : static int
172 0 : WordEntryCMP(WordEntry *a, WordEntry *b, char *buf)
173 : {
174 0 : return compareentry(a, b, buf);
175 : }
176 :
177 :
178 : Datum
179 3772 : tsvectorin(PG_FUNCTION_ARGS)
180 : {
181 3772 : char *buf = PG_GETARG_CSTRING(0);
182 3772 : Node *escontext = fcinfo->context;
183 : TSVectorParseState state;
184 : WordEntryIN *arr;
185 : int totallen;
186 : int arrlen; /* allocated size of arr */
187 : WordEntry *inarr;
188 3772 : int len = 0;
189 : TSVector in;
190 : 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 3772 : int buflen = 256; /* allocated size of tmpbuf */
205 :
206 3772 : state = init_tsvector_parser(buf, 0, escontext);
207 :
208 3772 : arrlen = 64;
209 3772 : arr = (WordEntryIN *) palloc(sizeof(WordEntryIN) * arrlen);
210 3772 : cur = tmpbuf = (char *) palloc(buflen);
211 :
212 184600 : while (gettoken_tsvector(state, &token, &toklen, &pos, &poslen, NULL))
213 : {
214 180828 : if (toklen >= MAXSTRLEN)
215 0 : ereturn(escontext, (Datum) 0,
216 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
217 : errmsg("word is too long (%ld bytes, max %ld bytes)",
218 : (long) toklen,
219 : (long) (MAXSTRLEN - 1))));
220 :
221 180828 : if (cur - tmpbuf > MAXSTRPOS)
222 0 : ereturn(escontext, (Datum) 0,
223 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
224 : 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 180828 : if (len >= arrlen)
231 : {
232 1314 : arrlen *= 2;
233 : arr = (WordEntryIN *)
234 1314 : repalloc(arr, sizeof(WordEntryIN) * arrlen);
235 : }
236 180828 : while ((cur - tmpbuf) + toklen >= buflen)
237 : {
238 0 : int dist = cur - tmpbuf;
239 :
240 0 : buflen *= 2;
241 0 : tmpbuf = (char *) repalloc(tmpbuf, buflen);
242 0 : cur = tmpbuf + dist;
243 : }
244 180828 : arr[len].entry.len = toklen;
245 180828 : arr[len].entry.pos = cur - tmpbuf;
246 180828 : memcpy(cur, token, toklen);
247 180828 : cur += toklen;
248 :
249 180828 : if (poslen != 0)
250 : {
251 9918 : arr[len].entry.haspos = 1;
252 9918 : arr[len].pos = pos;
253 9918 : arr[len].poslen = poslen;
254 : }
255 : else
256 : {
257 170910 : arr[len].entry.haspos = 0;
258 170910 : arr[len].pos = NULL;
259 170910 : arr[len].poslen = 0;
260 : }
261 180828 : len++;
262 : }
263 :
264 3766 : close_tsvector_parser(state);
265 :
266 : /* Did gettoken_tsvector fail? */
267 3766 : if (SOFT_ERROR_OCCURRED(escontext))
268 12 : PG_RETURN_NULL();
269 :
270 3754 : if (len > 0)
271 3706 : len = uniqueentry(arr, len, tmpbuf, &buflen);
272 : else
273 48 : buflen = 0;
274 :
275 3754 : if (buflen > MAXSTRPOS)
276 0 : ereturn(escontext, (Datum) 0,
277 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
278 : errmsg("string is too long for tsvector (%d bytes, max %d bytes)", buflen, MAXSTRPOS)));
279 :
280 3754 : totallen = CALCDATASIZE(len, buflen);
281 3754 : in = (TSVector) palloc0(totallen);
282 3754 : SET_VARSIZE(in, totallen);
283 3754 : in->size = len;
284 3754 : inarr = ARRPTR(in);
285 3754 : strbuf = STRPTR(in);
286 3754 : stroff = 0;
287 179056 : for (i = 0; i < len; i++)
288 : {
289 175302 : memcpy(strbuf + stroff, &tmpbuf[arr[i].entry.pos], arr[i].entry.len);
290 175302 : arr[i].entry.pos = stroff;
291 175302 : stroff += arr[i].entry.len;
292 175302 : if (arr[i].entry.haspos)
293 : {
294 : /* This should be unreachable because of MAXNUMPOS restrictions */
295 9606 : if (arr[i].poslen > 0xFFFF)
296 0 : elog(ERROR, "positions array too long");
297 :
298 : /* Copy number of positions */
299 9606 : stroff = SHORTALIGN(stroff);
300 9606 : *(uint16 *) (strbuf + stroff) = (uint16) arr[i].poslen;
301 9606 : stroff += sizeof(uint16);
302 :
303 : /* Copy positions */
304 9606 : memcpy(strbuf + stroff, arr[i].pos, arr[i].poslen * sizeof(WordEntryPos));
305 9606 : stroff += arr[i].poslen * sizeof(WordEntryPos);
306 :
307 9606 : pfree(arr[i].pos);
308 : }
309 175302 : inarr[i] = arr[i].entry;
310 : }
311 :
312 : Assert((strbuf + stroff - (char *) in) == totallen);
313 :
314 3754 : PG_RETURN_TSVECTOR(in);
315 : }
316 :
317 : Datum
318 4756 : tsvectorout(PG_FUNCTION_ARGS)
319 : {
320 4756 : TSVector out = PG_GETARG_TSVECTOR(0);
321 : char *outbuf;
322 : int32 i,
323 4756 : lenbuf = 0,
324 : pp;
325 4756 : WordEntry *ptr = ARRPTR(out);
326 : char *curbegin,
327 : *curin,
328 : *curout;
329 :
330 4756 : lenbuf = out->size * 2 /* '' */ + out->size - 1 /* space */ + 2 /* \0 */ ;
331 237878 : for (i = 0; i < out->size; i++)
332 : {
333 233122 : lenbuf += ptr[i].len * 2 * pg_database_encoding_max_length() /* for escape */ ;
334 233122 : if (ptr[i].haspos)
335 13118 : lenbuf += 1 /* : */ + 7 /* int2 + , + weight */ * POSDATALEN(out, &(ptr[i]));
336 : }
337 :
338 4756 : curout = outbuf = (char *) palloc(lenbuf);
339 237878 : for (i = 0; i < out->size; i++)
340 : {
341 233122 : curbegin = curin = STRPTR(out) + ptr->pos;
342 233122 : if (i != 0)
343 228552 : *curout++ = ' ';
344 233122 : *curout++ = '\'';
345 706132 : while (curin - curbegin < ptr->len)
346 : {
347 473010 : int len = pg_mblen(curin);
348 :
349 473010 : if (t_iseq(curin, '\''))
350 26 : *curout++ = '\'';
351 472984 : else if (t_iseq(curin, '\\'))
352 90 : *curout++ = '\\';
353 :
354 946020 : while (len--)
355 473010 : *curout++ = *curin++;
356 : }
357 :
358 233122 : *curout++ = '\'';
359 233122 : if ((pp = POSDATALEN(out, ptr)) != 0)
360 : {
361 : WordEntryPos *wptr;
362 :
363 13118 : *curout++ = ':';
364 13118 : wptr = POSDATAPTR(out, ptr);
365 27168 : while (pp)
366 : {
367 14050 : curout += sprintf(curout, "%d", WEP_GETPOS(*wptr));
368 14050 : switch (WEP_GETWEIGHT(*wptr))
369 : {
370 116 : case 3:
371 116 : *curout++ = 'A';
372 116 : break;
373 68 : case 2:
374 68 : *curout++ = 'B';
375 68 : break;
376 230 : case 1:
377 230 : *curout++ = 'C';
378 230 : break;
379 13636 : case 0:
380 : default:
381 13636 : break;
382 : }
383 :
384 14050 : if (pp > 1)
385 932 : *curout++ = ',';
386 14050 : pp--;
387 14050 : wptr++;
388 : }
389 : }
390 233122 : ptr++;
391 : }
392 :
393 4756 : *curout = '\0';
394 4756 : PG_FREE_IF_COPY(out, 0);
395 4756 : PG_RETURN_CSTRING(outbuf);
396 : }
397 :
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 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 :
419 0 : pq_begintypsend(&buf);
420 :
421 0 : pq_sendint32(&buf, vec->size);
422 0 : for (i = 0; i < vec->size; i++)
423 : {
424 : 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 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 : {
438 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 :
446 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
447 : }
448 :
449 : Datum
450 0 : tsvectorrecv(PG_FUNCTION_ARGS)
451 : {
452 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
453 : 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 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 :
469 0 : len = hdrlen * 2; /* times two to make room for lexemes */
470 0 : vec = (TSVector) palloc0(len);
471 0 : vec->size = nentries;
472 :
473 0 : datalen = 0;
474 0 : for (i = 0; i < nentries; i++)
475 : {
476 : const char *lexeme;
477 : uint16 npos;
478 : size_t lex_len;
479 :
480 0 : lexeme = pq_getmsgstring(buf);
481 0 : npos = (uint16) pq_getmsgint(buf, sizeof(uint16));
482 :
483 : /* sanity checks */
484 :
485 0 : lex_len = strlen(lexeme);
486 0 : if (lex_len > MAXSTRLEN)
487 0 : elog(ERROR, "invalid tsvector: lexeme too long");
488 :
489 0 : if (datalen > MAXSTRPOS)
490 0 : elog(ERROR, "invalid tsvector: maximum total lexeme length exceeded");
491 :
492 0 : if (npos > MAXNUMPOS)
493 0 : elog(ERROR, "unexpected number of tsvector positions");
494 :
495 : /*
496 : * Looks valid. Fill the WordEntry struct, and copy lexeme.
497 : *
498 : * But make sure the buffer is large enough first.
499 : */
500 0 : while (hdrlen + SHORTALIGN(datalen + lex_len) +
501 0 : (npos + 1) * sizeof(WordEntryPos) >= len)
502 : {
503 0 : len *= 2;
504 0 : vec = (TSVector) repalloc(vec, len);
505 : }
506 :
507 0 : vec->entries[i].haspos = (npos > 0) ? 1 : 0;
508 0 : vec->entries[i].len = lex_len;
509 0 : vec->entries[i].pos = datalen;
510 :
511 0 : memcpy(STRPTR(vec) + datalen, lexeme, lex_len);
512 :
513 0 : datalen += lex_len;
514 :
515 0 : if (i > 0 && WordEntryCMP(&vec->entries[i],
516 0 : &vec->entries[i - 1],
517 0 : STRPTR(vec)) <= 0)
518 0 : needSort = true;
519 :
520 : /* Receive positions */
521 0 : if (npos > 0)
522 : {
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 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 :
539 0 : wepptr = POSDATAPTR(vec, &vec->entries[i]);
540 0 : for (j = 0; j < npos; j++)
541 : {
542 0 : wepptr[j] = (WordEntryPos) pq_getmsgint(buf, sizeof(WordEntryPos));
543 0 : if (j > 0 && WEP_GETPOS(wepptr[j]) <= WEP_GETPOS(wepptr[j - 1]))
544 0 : elog(ERROR, "position information is misordered");
545 : }
546 :
547 0 : datalen += (npos + 1) * sizeof(WordEntry);
548 : }
549 : }
550 :
551 0 : SET_VARSIZE(vec, hdrlen + datalen);
552 :
553 0 : if (needSort)
554 0 : qsort_arg(ARRPTR(vec), vec->size, sizeof(WordEntry),
555 0 : compareentry, STRPTR(vec));
556 :
557 0 : PG_RETURN_TSVECTOR(vec);
558 : }
|