LCOV - code coverage report
Current view: top level - src/backend/utils/adt - tsvector.c (source / functions) Hit Total Coverage
Test: PostgreSQL 16beta1 Lines: 171 247 69.2 %
Date: 2023-06-06 08:12:15 Functions: 6 9 66.7 %
Legend: Lines: hit not hit

          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             : }

Generated by: LCOV version 1.14