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