Coverage Report

Created: 2026-08-13 07:12

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