LCOV - code coverage report
Current view: top level - src/backend/utils/adt - tsvector_op.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 86.5 % 1399 1210
Test Date: 2026-01-26 10:56:24 Functions: 78.8 % 52 41
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 60.7 % 896 544

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * tsvector_op.c
       4                 :             :  *        operations over tsvector
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7                 :             :  *
       8                 :             :  *
       9                 :             :  * IDENTIFICATION
      10                 :             :  *        src/backend/utils/adt/tsvector_op.c
      11                 :             :  *
      12                 :             :  *-------------------------------------------------------------------------
      13                 :             :  */
      14                 :             : #include "postgres.h"
      15                 :             : 
      16                 :             : #include <limits.h>
      17                 :             : 
      18                 :             : #include "access/htup_details.h"
      19                 :             : #include "catalog/namespace.h"
      20                 :             : #include "catalog/pg_type.h"
      21                 :             : #include "commands/trigger.h"
      22                 :             : #include "common/int.h"
      23                 :             : #include "executor/spi.h"
      24                 :             : #include "funcapi.h"
      25                 :             : #include "lib/qunique.h"
      26                 :             : #include "mb/pg_wchar.h"
      27                 :             : #include "miscadmin.h"
      28                 :             : #include "parser/parse_coerce.h"
      29                 :             : #include "tsearch/ts_utils.h"
      30                 :             : #include "utils/array.h"
      31                 :             : #include "utils/builtins.h"
      32                 :             : #include "utils/regproc.h"
      33                 :             : #include "utils/rel.h"
      34                 :             : 
      35                 :             : 
      36                 :             : typedef struct
      37                 :             : {
      38                 :             :         WordEntry  *arrb;
      39                 :             :         WordEntry  *arre;
      40                 :             :         char       *values;
      41                 :             :         char       *operand;
      42                 :             : } CHKVAL;
      43                 :             : 
      44                 :             : 
      45                 :             : typedef struct StatEntry
      46                 :             : {
      47                 :             :         uint32          ndoc;                   /* zero indicates that we were already here
      48                 :             :                                                                  * while walking through the tree */
      49                 :             :         uint32          nentry;
      50                 :             :         struct StatEntry *left;
      51                 :             :         struct StatEntry *right;
      52                 :             :         uint32          lenlexeme;
      53                 :             :         char            lexeme[FLEXIBLE_ARRAY_MEMBER];
      54                 :             : } StatEntry;
      55                 :             : 
      56                 :             : #define STATENTRYHDRSZ  (offsetof(StatEntry, lexeme))
      57                 :             : 
      58                 :             : typedef struct
      59                 :             : {
      60                 :             :         int32           weight;
      61                 :             : 
      62                 :             :         uint32          maxdepth;
      63                 :             : 
      64                 :             :         StatEntry **stack;
      65                 :             :         uint32          stackpos;
      66                 :             : 
      67                 :             :         StatEntry  *root;
      68                 :             : } TSVectorStat;
      69                 :             : 
      70                 :             : 
      71                 :             : static TSTernaryValue TS_execute_recurse(QueryItem *curitem, void *arg,
      72                 :             :                                                                                  uint32 flags,
      73                 :             :                                                                                  TSExecuteCallback chkcond);
      74                 :             : static bool TS_execute_locations_recurse(QueryItem *curitem,
      75                 :             :                                                                                  void *arg,
      76                 :             :                                                                                  TSExecuteCallback chkcond,
      77                 :             :                                                                                  List **locations);
      78                 :             : static int      tsvector_bsearch(const TSVectorData *tsv, char *lexeme, int lexeme_len);
      79                 :             : static Datum tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column);
      80                 :             : 
      81                 :             : 
      82                 :             : /*
      83                 :             :  * Order: haspos, len, word, for all positions (pos, weight)
      84                 :             :  */
      85                 :             : static int
      86                 :           0 : silly_cmp_tsvector(const TSVectorData *a, const TSVectorData *b)
      87                 :             : {
      88         [ #  # ]:           0 :         if (VARSIZE(a) < VARSIZE(b))
      89                 :           0 :                 return -1;
      90         [ #  # ]:           0 :         else if (VARSIZE(a) > VARSIZE(b))
      91                 :           0 :                 return 1;
      92         [ #  # ]:           0 :         else if (a->size < b->size)
      93                 :           0 :                 return -1;
      94         [ #  # ]:           0 :         else if (a->size > b->size)
      95                 :           0 :                 return 1;
      96                 :             :         else
      97                 :             :         {
      98                 :           0 :                 const WordEntry *aptr = ARRPTR(a);
      99                 :           0 :                 const WordEntry *bptr = ARRPTR(b);
     100                 :           0 :                 int                     i = 0;
     101                 :           0 :                 int                     res;
     102                 :             : 
     103                 :             : 
     104         [ #  # ]:           0 :                 for (i = 0; i < a->size; i++)
     105                 :             :                 {
     106         [ #  # ]:           0 :                         if (aptr->haspos != bptr->haspos)
     107                 :             :                         {
     108                 :           0 :                                 return (aptr->haspos > bptr->haspos) ? -1 : 1;
     109                 :             :                         }
     110         [ #  # ]:           0 :                         else if ((res = tsCompareString(STRPTR(a) + aptr->pos, aptr->len, STRPTR(b) + bptr->pos, bptr->len, false)) != 0)
     111                 :             :                         {
     112                 :           0 :                                 return res;
     113                 :             :                         }
     114         [ #  # ]:           0 :                         else if (aptr->haspos)
     115                 :             :                         {
     116                 :           0 :                                 WordEntryPos *ap = POSDATAPTR(a, aptr);
     117                 :           0 :                                 WordEntryPos *bp = POSDATAPTR(b, bptr);
     118                 :           0 :                                 int                     j;
     119                 :             : 
     120   [ #  #  #  #  :           0 :                                 if (POSDATALEN(a, aptr) != POSDATALEN(b, bptr))
                   #  # ]
     121   [ #  #  #  # ]:           0 :                                         return (POSDATALEN(a, aptr) > POSDATALEN(b, bptr)) ? -1 : 1;
     122                 :             : 
     123   [ #  #  #  # ]:           0 :                                 for (j = 0; j < POSDATALEN(a, aptr); j++)
     124                 :             :                                 {
     125         [ #  # ]:           0 :                                         if (WEP_GETPOS(*ap) != WEP_GETPOS(*bp))
     126                 :             :                                         {
     127                 :           0 :                                                 return (WEP_GETPOS(*ap) > WEP_GETPOS(*bp)) ? -1 : 1;
     128                 :             :                                         }
     129         [ #  # ]:           0 :                                         else if (WEP_GETWEIGHT(*ap) != WEP_GETWEIGHT(*bp))
     130                 :             :                                         {
     131                 :           0 :                                                 return (WEP_GETWEIGHT(*ap) > WEP_GETWEIGHT(*bp)) ? -1 : 1;
     132                 :             :                                         }
     133                 :           0 :                                         ap++, bp++;
     134                 :           0 :                                 }
     135         [ #  # ]:           0 :                         }
     136                 :             : 
     137                 :           0 :                         aptr++;
     138                 :           0 :                         bptr++;
     139                 :           0 :                 }
     140      [ #  #  # ]:           0 :         }
     141                 :             : 
     142                 :           0 :         return 0;
     143                 :           0 : }
     144                 :             : 
     145                 :             : #define TSVECTORCMPFUNC( type, action, ret )                    \
     146                 :             : Datum                                                                                                   \
     147                 :             : tsvector_##type(PG_FUNCTION_ARGS)                                               \
     148                 :             : {                                                                                                               \
     149                 :             :         TSVector        a = PG_GETARG_TSVECTOR(0);                              \
     150                 :             :         TSVector        b = PG_GETARG_TSVECTOR(1);                              \
     151                 :             :         int                     res = silly_cmp_tsvector(a, b);                 \
     152                 :             :         PG_FREE_IF_COPY(a,0);                                                           \
     153                 :             :         PG_FREE_IF_COPY(b,1);                                                           \
     154                 :             :         PG_RETURN_##ret( res action 0 );                                        \
     155                 :             : }       \
     156                 :             : /* keep compiler quiet - no extra ; */                                  \
     157                 :             : extern int no_such_variable
     158                 :             : 
     159   [ #  #  #  # ]:           0 : TSVECTORCMPFUNC(lt, <, BOOL);
     160   [ #  #  #  # ]:           0 : TSVECTORCMPFUNC(le, <=, BOOL);
     161   [ #  #  #  # ]:           0 : TSVECTORCMPFUNC(eq, ==, BOOL);
     162   [ #  #  #  # ]:           0 : TSVECTORCMPFUNC(ge, >=, BOOL);
     163   [ #  #  #  # ]:           0 : TSVECTORCMPFUNC(gt, >, BOOL);
     164   [ #  #  #  # ]:           0 : TSVECTORCMPFUNC(ne, !=, BOOL);
     165   [ #  #  #  # ]:           0 : TSVECTORCMPFUNC(cmp, +, INT32);
     166                 :             : 
     167                 :             : Datum
     168                 :          15 : tsvector_strip(PG_FUNCTION_ARGS)
     169                 :             : {
     170                 :          15 :         TSVector        in = PG_GETARG_TSVECTOR(0);
     171                 :          15 :         TSVector        out;
     172                 :          15 :         int                     i,
     173                 :          15 :                                 len = 0;
     174                 :          15 :         WordEntry  *arrin = ARRPTR(in),
     175                 :             :                            *arrout;
     176                 :          15 :         char       *cur;
     177                 :             : 
     178         [ +  + ]:          53 :         for (i = 0; i < in->size; i++)
     179                 :          38 :                 len += arrin[i].len;
     180                 :             : 
     181                 :          15 :         len = CALCDATASIZE(in->size, len);
     182                 :          15 :         out = (TSVector) palloc0(len);
     183                 :          15 :         SET_VARSIZE(out, len);
     184                 :          15 :         out->size = in->size;
     185                 :          15 :         arrout = ARRPTR(out);
     186                 :          15 :         cur = STRPTR(out);
     187         [ +  + ]:          53 :         for (i = 0; i < in->size; i++)
     188                 :             :         {
     189                 :          38 :                 memcpy(cur, STRPTR(in) + arrin[i].pos, arrin[i].len);
     190                 :          38 :                 arrout[i].haspos = 0;
     191                 :          38 :                 arrout[i].len = arrin[i].len;
     192                 :          38 :                 arrout[i].pos = cur - STRPTR(out);
     193                 :          38 :                 cur += arrout[i].len;
     194                 :          38 :         }
     195                 :             : 
     196         [ +  - ]:          15 :         PG_FREE_IF_COPY(in, 0);
     197                 :          30 :         PG_RETURN_POINTER(out);
     198                 :          15 : }
     199                 :             : 
     200                 :             : Datum
     201                 :           1 : tsvector_length(PG_FUNCTION_ARGS)
     202                 :             : {
     203                 :           1 :         TSVector        in = PG_GETARG_TSVECTOR(0);
     204                 :           1 :         int32           ret = in->size;
     205                 :             : 
     206         [ +  - ]:           1 :         PG_FREE_IF_COPY(in, 0);
     207                 :           2 :         PG_RETURN_INT32(ret);
     208                 :           1 : }
     209                 :             : 
     210                 :             : Datum
     211                 :           2 : tsvector_setweight(PG_FUNCTION_ARGS)
     212                 :             : {
     213                 :           2 :         TSVector        in = PG_GETARG_TSVECTOR(0);
     214                 :           2 :         char            cw = PG_GETARG_CHAR(1);
     215                 :           2 :         TSVector        out;
     216                 :           2 :         int                     i,
     217                 :             :                                 j;
     218                 :           2 :         WordEntry  *entry;
     219                 :           2 :         WordEntryPos *p;
     220                 :           2 :         int                     w = 0;
     221                 :             : 
     222   [ -  -  +  -  :           2 :         switch (cw)
                      - ]
     223                 :             :         {
     224                 :             :                 case 'A':
     225                 :             :                 case 'a':
     226                 :           0 :                         w = 3;
     227                 :           0 :                         break;
     228                 :             :                 case 'B':
     229                 :             :                 case 'b':
     230                 :           0 :                         w = 2;
     231                 :           0 :                         break;
     232                 :             :                 case 'C':
     233                 :             :                 case 'c':
     234                 :           2 :                         w = 1;
     235                 :           2 :                         break;
     236                 :             :                 case 'D':
     237                 :             :                 case 'd':
     238                 :           0 :                         w = 0;
     239                 :           0 :                         break;
     240                 :             :                 default:
     241                 :             :                         /* internal error */
     242   [ #  #  #  # ]:           0 :                         elog(ERROR, "unrecognized weight: %d", cw);
     243                 :           0 :         }
     244                 :             : 
     245                 :           2 :         out = (TSVector) palloc(VARSIZE(in));
     246                 :           2 :         memcpy(out, in, VARSIZE(in));
     247                 :           2 :         entry = ARRPTR(out);
     248                 :           2 :         i = out->size;
     249         [ +  + ]:          10 :         while (i--)
     250                 :             :         {
     251   [ +  -  +  - ]:           8 :                 if ((j = POSDATALEN(out, entry)) != 0)
     252                 :             :                 {
     253                 :           8 :                         p = POSDATAPTR(out, entry);
     254         [ +  + ]:          28 :                         while (j--)
     255                 :             :                         {
     256                 :          20 :                                 WEP_SETWEIGHT(*p, w);
     257                 :          20 :                                 p++;
     258                 :             :                         }
     259                 :           8 :                 }
     260                 :           8 :                 entry++;
     261                 :             :         }
     262                 :             : 
     263         [ +  - ]:           2 :         PG_FREE_IF_COPY(in, 0);
     264                 :           4 :         PG_RETURN_POINTER(out);
     265                 :           2 : }
     266                 :             : 
     267                 :             : /*
     268                 :             :  * setweight(tsin tsvector, char_weight "char", lexemes "text"[])
     269                 :             :  *
     270                 :             :  * Assign weight w to elements of tsin that are listed in lexemes.
     271                 :             :  */
     272                 :             : Datum
     273                 :           4 : tsvector_setweight_by_filter(PG_FUNCTION_ARGS)
     274                 :             : {
     275                 :           4 :         TSVector        tsin = PG_GETARG_TSVECTOR(0);
     276                 :           4 :         char            char_weight = PG_GETARG_CHAR(1);
     277                 :           4 :         ArrayType  *lexemes = PG_GETARG_ARRAYTYPE_P(2);
     278                 :             : 
     279                 :           4 :         TSVector        tsout;
     280                 :           4 :         int                     i,
     281                 :             :                                 j,
     282                 :             :                                 nlexemes,
     283                 :             :                                 weight;
     284                 :           4 :         WordEntry  *entry;
     285                 :           4 :         Datum      *dlexemes;
     286                 :           4 :         bool       *nulls;
     287                 :             : 
     288   [ -  -  +  -  :           4 :         switch (char_weight)
                      - ]
     289                 :             :         {
     290                 :             :                 case 'A':
     291                 :             :                 case 'a':
     292                 :           0 :                         weight = 3;
     293                 :           0 :                         break;
     294                 :             :                 case 'B':
     295                 :             :                 case 'b':
     296                 :           0 :                         weight = 2;
     297                 :           0 :                         break;
     298                 :             :                 case 'C':
     299                 :             :                 case 'c':
     300                 :           4 :                         weight = 1;
     301                 :           4 :                         break;
     302                 :             :                 case 'D':
     303                 :             :                 case 'd':
     304                 :           0 :                         weight = 0;
     305                 :           0 :                         break;
     306                 :             :                 default:
     307                 :             :                         /* internal error */
     308   [ #  #  #  # ]:           0 :                         elog(ERROR, "unrecognized weight: %c", char_weight);
     309                 :           0 :         }
     310                 :             : 
     311                 :           4 :         tsout = (TSVector) palloc(VARSIZE(tsin));
     312                 :           4 :         memcpy(tsout, tsin, VARSIZE(tsin));
     313                 :           4 :         entry = ARRPTR(tsout);
     314                 :             : 
     315                 :           4 :         deconstruct_array_builtin(lexemes, TEXTOID, &dlexemes, &nulls, &nlexemes);
     316                 :             : 
     317                 :             :         /*
     318                 :             :          * Assuming that lexemes array is significantly shorter than tsvector we
     319                 :             :          * can iterate through lexemes performing binary search of each lexeme
     320                 :             :          * from lexemes in tsvector.
     321                 :             :          */
     322         [ +  + ]:          12 :         for (i = 0; i < nlexemes; i++)
     323                 :             :         {
     324                 :           8 :                 char       *lex;
     325                 :           8 :                 int                     lex_len,
     326                 :             :                                         lex_pos;
     327                 :             : 
     328                 :             :                 /* Ignore null array elements, they surely don't match */
     329         [ +  + ]:           8 :                 if (nulls[i])
     330                 :           1 :                         continue;
     331                 :             : 
     332                 :           7 :                 lex = VARDATA(DatumGetPointer(dlexemes[i]));
     333                 :           7 :                 lex_len = VARSIZE(DatumGetPointer(dlexemes[i])) - VARHDRSZ;
     334                 :           7 :                 lex_pos = tsvector_bsearch(tsout, lex, lex_len);
     335                 :             : 
     336   [ +  +  +  +  :           7 :                 if (lex_pos >= 0 && (j = POSDATALEN(tsout, entry + lex_pos)) != 0)
                   +  + ]
     337                 :             :                 {
     338                 :           4 :                         WordEntryPos *p = POSDATAPTR(tsout, entry + lex_pos);
     339                 :             : 
     340         [ +  + ]:          13 :                         while (j--)
     341                 :             :                         {
     342                 :           9 :                                 WEP_SETWEIGHT(*p, weight);
     343                 :           9 :                                 p++;
     344                 :             :                         }
     345                 :           4 :                 }
     346      [ -  +  + ]:           8 :         }
     347                 :             : 
     348         [ -  + ]:           4 :         PG_FREE_IF_COPY(tsin, 0);
     349         [ -  + ]:           4 :         PG_FREE_IF_COPY(lexemes, 2);
     350                 :             : 
     351                 :           8 :         PG_RETURN_POINTER(tsout);
     352                 :           4 : }
     353                 :             : 
     354                 :             : #define compareEntry(pa, a, pb, b) \
     355                 :             :         tsCompareString((pa) + (a)->pos, (a)->len,        \
     356                 :             :                                         (pb) + (b)->pos, (b)->len,        \
     357                 :             :                                         false)
     358                 :             : 
     359                 :             : /*
     360                 :             :  * Add positions from src to dest after offsetting them by maxpos.
     361                 :             :  * Return the number added (might be less than expected due to overflow)
     362                 :             :  */
     363                 :             : static int32
     364                 :           2 : add_pos(TSVector src, WordEntry *srcptr,
     365                 :             :                 TSVector dest, WordEntry *destptr,
     366                 :             :                 int32 maxpos)
     367                 :             : {
     368                 :           2 :         uint16     *clen = &_POSVECPTR(dest, destptr)->npos;
     369                 :           2 :         int                     i;
     370         [ +  - ]:           2 :         uint16          slen = POSDATALEN(src, srcptr),
     371                 :             :                                 startlen;
     372                 :           2 :         WordEntryPos *spos = POSDATAPTR(src, srcptr),
     373                 :           2 :                            *dpos = POSDATAPTR(dest, destptr);
     374                 :             : 
     375         [ +  - ]:           2 :         if (!destptr->haspos)
     376                 :           0 :                 *clen = 0;
     377                 :             : 
     378                 :           2 :         startlen = *clen;
     379         [ +  + ]:           8 :         for (i = 0;
     380   [ +  +  -  + ]:           4 :                  i < slen && *clen < MAXNUMPOS &&
     381         [ +  + ]:           2 :                  (*clen == 0 || WEP_GETPOS(dpos[*clen - 1]) != MAXENTRYPOS - 1);
     382                 :           2 :                  i++)
     383                 :             :         {
     384                 :           2 :                 WEP_SETWEIGHT(dpos[*clen], WEP_GETWEIGHT(spos[i]));
     385         [ -  + ]:           2 :                 WEP_SETPOS(dpos[*clen], LIMITPOS(WEP_GETPOS(spos[i]) + maxpos));
     386                 :           2 :                 (*clen)++;
     387                 :           2 :         }
     388                 :             : 
     389         [ -  + ]:           2 :         if (*clen != startlen)
     390                 :           2 :                 destptr->haspos = 1;
     391                 :           4 :         return *clen - startlen;
     392                 :           2 : }
     393                 :             : 
     394                 :             : /*
     395                 :             :  * Perform binary search of given lexeme in TSVector.
     396                 :             :  * Returns lexeme position in TSVector's entry array or -1 if lexeme wasn't
     397                 :             :  * found.
     398                 :             :  */
     399                 :             : static int
     400                 :          33 : tsvector_bsearch(const TSVectorData *tsv, char *lexeme, int lexeme_len)
     401                 :             : {
     402                 :          33 :         const WordEntry *arrin = ARRPTR(tsv);
     403                 :          66 :         int                     StopLow = 0,
     404                 :          33 :                                 StopHigh = tsv->size,
     405                 :             :                                 StopMiddle,
     406                 :             :                                 cmp;
     407                 :             : 
     408         [ +  + ]:          87 :         while (StopLow < StopHigh)
     409                 :             :         {
     410                 :          77 :                 StopMiddle = (StopLow + StopHigh) / 2;
     411                 :             : 
     412                 :         154 :                 cmp = tsCompareString(lexeme, lexeme_len,
     413                 :          77 :                                                           STRPTR(tsv) + arrin[StopMiddle].pos,
     414                 :          77 :                                                           arrin[StopMiddle].len,
     415                 :             :                                                           false);
     416                 :             : 
     417         [ +  + ]:          77 :                 if (cmp < 0)
     418                 :          36 :                         StopHigh = StopMiddle;
     419         [ +  + ]:          41 :                 else if (cmp > 0)
     420                 :          18 :                         StopLow = StopMiddle + 1;
     421                 :             :                 else                                    /* found it */
     422                 :          23 :                         return StopMiddle;
     423                 :             :         }
     424                 :             : 
     425                 :          10 :         return -1;
     426                 :          33 : }
     427                 :             : 
     428                 :             : /*
     429                 :             :  * qsort comparator functions
     430                 :             :  */
     431                 :             : 
     432                 :             : static int
     433                 :          13 : compare_int(const void *va, const void *vb)
     434                 :             : {
     435                 :          13 :         int                     a = *((const int *) va);
     436                 :          13 :         int                     b = *((const int *) vb);
     437                 :             : 
     438                 :          26 :         return pg_cmp_s32(a, b);
     439                 :          13 : }
     440                 :             : 
     441                 :             : static int
     442                 :          17 : compare_text_lexemes(const void *va, const void *vb)
     443                 :             : {
     444                 :          17 :         Datum           a = *((const Datum *) va);
     445                 :          17 :         Datum           b = *((const Datum *) vb);
     446                 :          17 :         char       *alex = VARDATA_ANY(DatumGetPointer(a));
     447                 :          17 :         int                     alex_len = VARSIZE_ANY_EXHDR(DatumGetPointer(a));
     448                 :          17 :         char       *blex = VARDATA_ANY(DatumGetPointer(b));
     449                 :          17 :         int                     blex_len = VARSIZE_ANY_EXHDR(DatumGetPointer(b));
     450                 :             : 
     451                 :          34 :         return tsCompareString(alex, alex_len, blex, blex_len, false);
     452                 :          17 : }
     453                 :             : 
     454                 :             : /*
     455                 :             :  * Internal routine to delete lexemes from TSVector by array of offsets.
     456                 :             :  *
     457                 :             :  * int *indices_to_delete -- array of lexeme offsets to delete (modified here!)
     458                 :             :  * int indices_count -- size of that array
     459                 :             :  *
     460                 :             :  * Returns new TSVector without given lexemes along with their positions
     461                 :             :  * and weights.
     462                 :             :  */
     463                 :             : static TSVector
     464                 :          11 : tsvector_delete_by_indices(TSVector tsv, int *indices_to_delete,
     465                 :             :                                                    int indices_count)
     466                 :             : {
     467                 :          11 :         TSVector        tsout;
     468                 :          11 :         WordEntry  *arrin = ARRPTR(tsv),
     469                 :             :                            *arrout;
     470                 :          11 :         char       *data = STRPTR(tsv),
     471                 :             :                            *dataout;
     472                 :          11 :         int                     i,                              /* index in arrin */
     473                 :             :                                 j,                              /* index in arrout */
     474                 :             :                                 k,                              /* index in indices_to_delete */
     475                 :             :                                 curoff;                 /* index in dataout area */
     476                 :             : 
     477                 :             :         /*
     478                 :             :          * Sort the filter array to simplify membership checks below.  Also, get
     479                 :             :          * rid of any duplicate entries, so that we can assume that indices_count
     480                 :             :          * is exactly equal to the number of lexemes that will be removed.
     481                 :             :          */
     482         [ +  + ]:          11 :         if (indices_count > 1)
     483                 :             :         {
     484                 :           5 :                 qsort(indices_to_delete, indices_count, sizeof(int), compare_int);
     485                 :           5 :                 indices_count = qunique(indices_to_delete, indices_count, sizeof(int),
     486                 :             :                                                                 compare_int);
     487                 :           5 :         }
     488                 :             : 
     489                 :             :         /*
     490                 :             :          * Here we overestimate tsout size, since we don't know how much space is
     491                 :             :          * used by the deleted lexeme(s).  We will set exact size below.
     492                 :             :          */
     493                 :          11 :         tsout = (TSVector) palloc0(VARSIZE(tsv));
     494                 :             : 
     495                 :             :         /* This count must be correct because STRPTR(tsout) relies on it. */
     496                 :          11 :         tsout->size = tsv->size - indices_count;
     497                 :             : 
     498                 :             :         /*
     499                 :             :          * Copy tsv to tsout, skipping lexemes listed in indices_to_delete.
     500                 :             :          */
     501                 :          11 :         arrout = ARRPTR(tsout);
     502                 :          11 :         dataout = STRPTR(tsout);
     503                 :          11 :         curoff = 0;
     504         [ +  + ]:          66 :         for (i = j = k = 0; i < tsv->size; i++)
     505                 :             :         {
     506                 :             :                 /*
     507                 :             :                  * If current i is present in indices_to_delete, skip this lexeme.
     508                 :             :                  * Since indices_to_delete is already sorted, we only need to check
     509                 :             :                  * the current (k'th) entry.
     510                 :             :                  */
     511   [ +  +  +  + ]:          55 :                 if (k < indices_count && i == indices_to_delete[k])
     512                 :             :                 {
     513                 :          16 :                         k++;
     514                 :          16 :                         continue;
     515                 :             :                 }
     516                 :             : 
     517                 :             :                 /* Copy lexeme and its positions and weights */
     518                 :          39 :                 memcpy(dataout + curoff, data + arrin[i].pos, arrin[i].len);
     519                 :          39 :                 arrout[j].haspos = arrin[i].haspos;
     520                 :          39 :                 arrout[j].len = arrin[i].len;
     521                 :          39 :                 arrout[j].pos = curoff;
     522                 :          39 :                 curoff += arrin[i].len;
     523         [ +  + ]:          39 :                 if (arrin[i].haspos)
     524                 :             :                 {
     525         [ +  - ]:          26 :                         int                     len = POSDATALEN(tsv, arrin + i) * sizeof(WordEntryPos)
     526                 :          26 :                                 + sizeof(uint16);
     527                 :             : 
     528                 :          26 :                         curoff = SHORTALIGN(curoff);
     529                 :          26 :                         memcpy(dataout + curoff,
     530                 :             :                                    STRPTR(tsv) + SHORTALIGN(arrin[i].pos + arrin[i].len),
     531                 :             :                                    len);
     532                 :          26 :                         curoff += len;
     533                 :          26 :                 }
     534                 :             : 
     535                 :          39 :                 j++;
     536                 :          39 :         }
     537                 :             : 
     538                 :             :         /*
     539                 :             :          * k should now be exactly equal to indices_count. If it isn't then the
     540                 :             :          * caller provided us with indices outside of [0, tsv->size) range and
     541                 :             :          * estimation of tsout's size is wrong.
     542                 :             :          */
     543         [ +  - ]:          11 :         Assert(k == indices_count);
     544                 :             : 
     545                 :          11 :         SET_VARSIZE(tsout, CALCDATASIZE(tsout->size, curoff));
     546                 :          22 :         return tsout;
     547                 :          11 : }
     548                 :             : 
     549                 :             : /*
     550                 :             :  * Delete given lexeme from tsvector.
     551                 :             :  * Implementation of user-level ts_delete(tsvector, text).
     552                 :             :  */
     553                 :             : Datum
     554                 :           6 : tsvector_delete_str(PG_FUNCTION_ARGS)
     555                 :             : {
     556                 :           6 :         TSVector        tsin = PG_GETARG_TSVECTOR(0),
     557                 :             :                                 tsout;
     558                 :           6 :         text       *tlexeme = PG_GETARG_TEXT_PP(1);
     559                 :           6 :         char       *lexeme = VARDATA_ANY(tlexeme);
     560                 :           6 :         int                     lexeme_len = VARSIZE_ANY_EXHDR(tlexeme),
     561                 :             :                                 skip_index;
     562                 :             : 
     563         [ +  + ]:           6 :         if ((skip_index = tsvector_bsearch(tsin, lexeme, lexeme_len)) == -1)
     564                 :           2 :                 PG_RETURN_POINTER(tsin);
     565                 :             : 
     566                 :           4 :         tsout = tsvector_delete_by_indices(tsin, &skip_index, 1);
     567                 :             : 
     568         [ +  - ]:           4 :         PG_FREE_IF_COPY(tsin, 0);
     569         [ +  - ]:           4 :         PG_FREE_IF_COPY(tlexeme, 1);
     570                 :           4 :         PG_RETURN_POINTER(tsout);
     571                 :           6 : }
     572                 :             : 
     573                 :             : /*
     574                 :             :  * Delete given array of lexemes from tsvector.
     575                 :             :  * Implementation of user-level ts_delete(tsvector, text[]).
     576                 :             :  */
     577                 :             : Datum
     578                 :           7 : tsvector_delete_arr(PG_FUNCTION_ARGS)
     579                 :             : {
     580                 :           7 :         TSVector        tsin = PG_GETARG_TSVECTOR(0),
     581                 :             :                                 tsout;
     582                 :           7 :         ArrayType  *lexemes = PG_GETARG_ARRAYTYPE_P(1);
     583                 :           7 :         int                     i,
     584                 :             :                                 nlex,
     585                 :             :                                 skip_count,
     586                 :             :                            *skip_indices;
     587                 :           7 :         Datum      *dlexemes;
     588                 :           7 :         bool       *nulls;
     589                 :             : 
     590                 :           7 :         deconstruct_array_builtin(lexemes, TEXTOID, &dlexemes, &nulls, &nlex);
     591                 :             : 
     592                 :             :         /*
     593                 :             :          * In typical use case array of lexemes to delete is relatively small. So
     594                 :             :          * here we optimize things for that scenario: iterate through lexarr
     595                 :             :          * performing binary search of each lexeme from lexarr in tsvector.
     596                 :             :          */
     597                 :           7 :         skip_indices = palloc0(nlex * sizeof(int));
     598         [ +  + ]:          28 :         for (i = skip_count = 0; i < nlex; i++)
     599                 :             :         {
     600                 :          21 :                 char       *lex;
     601                 :          21 :                 int                     lex_len,
     602                 :             :                                         lex_pos;
     603                 :             : 
     604                 :             :                 /* Ignore null array elements, they surely don't match */
     605         [ +  + ]:          21 :                 if (nulls[i])
     606                 :           1 :                         continue;
     607                 :             : 
     608                 :          20 :                 lex = VARDATA(DatumGetPointer(dlexemes[i]));
     609                 :          20 :                 lex_len = VARSIZE(DatumGetPointer(dlexemes[i])) - VARHDRSZ;
     610                 :          20 :                 lex_pos = tsvector_bsearch(tsin, lex, lex_len);
     611                 :             : 
     612         [ +  + ]:          20 :                 if (lex_pos >= 0)
     613                 :          13 :                         skip_indices[skip_count++] = lex_pos;
     614      [ -  +  + ]:          21 :         }
     615                 :             : 
     616                 :           7 :         tsout = tsvector_delete_by_indices(tsin, skip_indices, skip_count);
     617                 :             : 
     618                 :           7 :         pfree(skip_indices);
     619         [ +  - ]:           7 :         PG_FREE_IF_COPY(tsin, 0);
     620         [ +  - ]:           7 :         PG_FREE_IF_COPY(lexemes, 1);
     621                 :             : 
     622                 :          14 :         PG_RETURN_POINTER(tsout);
     623                 :           7 : }
     624                 :             : 
     625                 :             : /*
     626                 :             :  * Expand tsvector as table with following columns:
     627                 :             :  *         lexeme: lexeme text
     628                 :             :  *         positions: integer array of lexeme positions
     629                 :             :  *         weights: char array of weights corresponding to positions
     630                 :             :  */
     631                 :             : Datum
     632                 :          30 : tsvector_unnest(PG_FUNCTION_ARGS)
     633                 :             : {
     634                 :          30 :         FuncCallContext *funcctx;
     635                 :          30 :         TSVector        tsin;
     636                 :             : 
     637         [ +  + ]:          30 :         if (SRF_IS_FIRSTCALL())
     638                 :             :         {
     639                 :           5 :                 MemoryContext oldcontext;
     640                 :           5 :                 TupleDesc       tupdesc;
     641                 :             : 
     642                 :           5 :                 funcctx = SRF_FIRSTCALL_INIT();
     643                 :           5 :                 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
     644                 :             : 
     645                 :           5 :                 tupdesc = CreateTemplateTupleDesc(3);
     646                 :           5 :                 TupleDescInitEntry(tupdesc, (AttrNumber) 1, "lexeme",
     647                 :             :                                                    TEXTOID, -1, 0);
     648                 :           5 :                 TupleDescInitEntry(tupdesc, (AttrNumber) 2, "positions",
     649                 :             :                                                    INT2ARRAYOID, -1, 0);
     650                 :           5 :                 TupleDescInitEntry(tupdesc, (AttrNumber) 3, "weights",
     651                 :             :                                                    TEXTARRAYOID, -1, 0);
     652         [ +  - ]:           5 :                 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     653   [ #  #  #  # ]:           0 :                         elog(ERROR, "return type must be a row type");
     654                 :           5 :                 funcctx->tuple_desc = tupdesc;
     655                 :             : 
     656                 :           5 :                 funcctx->user_fctx = PG_GETARG_TSVECTOR_COPY(0);
     657                 :             : 
     658                 :           5 :                 MemoryContextSwitchTo(oldcontext);
     659                 :           5 :         }
     660                 :             : 
     661                 :          30 :         funcctx = SRF_PERCALL_SETUP();
     662                 :          30 :         tsin = (TSVector) funcctx->user_fctx;
     663                 :             : 
     664         [ +  + ]:          30 :         if (funcctx->call_cntr < tsin->size)
     665                 :             :         {
     666                 :          25 :                 WordEntry  *arrin = ARRPTR(tsin);
     667                 :          25 :                 char       *data = STRPTR(tsin);
     668                 :          25 :                 HeapTuple       tuple;
     669                 :          25 :                 int                     j,
     670                 :          25 :                                         i = funcctx->call_cntr;
     671                 :          25 :                 bool            nulls[] = {false, false, false};
     672                 :          25 :                 Datum           values[3];
     673                 :             : 
     674                 :          25 :                 values[0] = PointerGetDatum(cstring_to_text_with_len(data + arrin[i].pos, arrin[i].len));
     675                 :             : 
     676         [ +  + ]:          25 :                 if (arrin[i].haspos)
     677                 :             :                 {
     678                 :          15 :                         WordEntryPosVector *posv;
     679                 :          15 :                         Datum      *positions;
     680                 :          15 :                         Datum      *weights;
     681                 :          15 :                         char            weight;
     682                 :             : 
     683                 :             :                         /*
     684                 :             :                          * Internally tsvector stores position and weight in the same
     685                 :             :                          * uint16 (2 bits for weight, 14 for position). Here we extract
     686                 :             :                          * that in two separate arrays.
     687                 :             :                          */
     688                 :          15 :                         posv = _POSVECPTR(tsin, arrin + i);
     689                 :          15 :                         positions = palloc(posv->npos * sizeof(Datum));
     690                 :          15 :                         weights = palloc(posv->npos * sizeof(Datum));
     691         [ +  + ]:          42 :                         for (j = 0; j < posv->npos; j++)
     692                 :             :                         {
     693                 :          27 :                                 positions[j] = Int16GetDatum(WEP_GETPOS(posv->pos[j]));
     694                 :          27 :                                 weight = 'D' - WEP_GETWEIGHT(posv->pos[j]);
     695                 :          27 :                                 weights[j] = PointerGetDatum(cstring_to_text_with_len(&weight,
     696                 :             :                                                                                                                                           1));
     697                 :          27 :                         }
     698                 :             : 
     699                 :          15 :                         values[1] = PointerGetDatum(construct_array_builtin(positions, posv->npos, INT2OID));
     700                 :          15 :                         values[2] = PointerGetDatum(construct_array_builtin(weights, posv->npos, TEXTOID));
     701                 :          15 :                 }
     702                 :             :                 else
     703                 :             :                 {
     704                 :          10 :                         nulls[1] = nulls[2] = true;
     705                 :             :                 }
     706                 :             : 
     707                 :          25 :                 tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
     708                 :          25 :                 SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
     709         [ +  - ]:          25 :         }
     710                 :             :         else
     711                 :             :         {
     712         [ +  - ]:           5 :                 SRF_RETURN_DONE(funcctx);
     713                 :             :         }
     714         [ -  + ]:          30 : }
     715                 :             : 
     716                 :             : /*
     717                 :             :  * Convert tsvector to array of lexemes.
     718                 :             :  */
     719                 :             : Datum
     720                 :           2 : tsvector_to_array(PG_FUNCTION_ARGS)
     721                 :             : {
     722                 :           2 :         TSVector        tsin = PG_GETARG_TSVECTOR(0);
     723                 :           2 :         WordEntry  *arrin = ARRPTR(tsin);
     724                 :           2 :         Datum      *elements;
     725                 :           2 :         int                     i;
     726                 :           2 :         ArrayType  *array;
     727                 :             : 
     728                 :           2 :         elements = palloc(tsin->size * sizeof(Datum));
     729                 :             : 
     730         [ +  + ]:          12 :         for (i = 0; i < tsin->size; i++)
     731                 :             :         {
     732                 :          20 :                 elements[i] = PointerGetDatum(cstring_to_text_with_len(STRPTR(tsin) + arrin[i].pos,
     733                 :          10 :                                                                                                                            arrin[i].len));
     734                 :          10 :         }
     735                 :             : 
     736                 :           2 :         array = construct_array_builtin(elements, tsin->size, TEXTOID);
     737                 :             : 
     738                 :           2 :         pfree(elements);
     739         [ +  - ]:           2 :         PG_FREE_IF_COPY(tsin, 0);
     740                 :           4 :         PG_RETURN_POINTER(array);
     741                 :           2 : }
     742                 :             : 
     743                 :             : /*
     744                 :             :  * Build tsvector from array of lexemes.
     745                 :             :  */
     746                 :             : Datum
     747                 :           4 : array_to_tsvector(PG_FUNCTION_ARGS)
     748                 :             : {
     749                 :           4 :         ArrayType  *v = PG_GETARG_ARRAYTYPE_P(0);
     750                 :           4 :         TSVector        tsout;
     751                 :           4 :         Datum      *dlexemes;
     752                 :           4 :         WordEntry  *arrout;
     753                 :           4 :         bool       *nulls;
     754                 :           4 :         int                     nitems,
     755                 :             :                                 i,
     756                 :             :                                 tslen,
     757                 :           4 :                                 datalen = 0;
     758                 :           4 :         char       *cur;
     759                 :             : 
     760                 :           4 :         deconstruct_array_builtin(v, TEXTOID, &dlexemes, &nulls, &nitems);
     761                 :             : 
     762                 :             :         /*
     763                 :             :          * Reject nulls and zero length strings (maybe we should just ignore them,
     764                 :             :          * instead?)
     765                 :             :          */
     766         [ +  + ]:          21 :         for (i = 0; i < nitems; i++)
     767                 :             :         {
     768         [ +  + ]:          19 :                 if (nulls[i])
     769   [ +  -  +  - ]:           1 :                         ereport(ERROR,
     770                 :             :                                         (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
     771                 :             :                                          errmsg("lexeme array may not contain nulls")));
     772                 :             : 
     773         [ +  + ]:          18 :                 if (VARSIZE(DatumGetPointer(dlexemes[i])) - VARHDRSZ == 0)
     774   [ +  -  +  - ]:           1 :                         ereport(ERROR,
     775                 :             :                                         (errcode(ERRCODE_ZERO_LENGTH_CHARACTER_STRING),
     776                 :             :                                          errmsg("lexeme array may not contain empty strings")));
     777                 :          17 :         }
     778                 :             : 
     779                 :             :         /* Sort and de-dup, because this is required for a valid tsvector. */
     780         [ -  + ]:           2 :         if (nitems > 1)
     781                 :             :         {
     782                 :           2 :                 qsort(dlexemes, nitems, sizeof(Datum), compare_text_lexemes);
     783                 :           2 :                 nitems = qunique(dlexemes, nitems, sizeof(Datum),
     784                 :             :                                                  compare_text_lexemes);
     785                 :           2 :         }
     786                 :             : 
     787                 :             :         /* Calculate space needed for surviving lexemes. */
     788         [ +  + ]:          10 :         for (i = 0; i < nitems; i++)
     789                 :           8 :                 datalen += VARSIZE(DatumGetPointer(dlexemes[i])) - VARHDRSZ;
     790                 :           2 :         tslen = CALCDATASIZE(nitems, datalen);
     791                 :             : 
     792                 :             :         /* Allocate and fill tsvector. */
     793                 :           2 :         tsout = (TSVector) palloc0(tslen);
     794                 :           2 :         SET_VARSIZE(tsout, tslen);
     795                 :           2 :         tsout->size = nitems;
     796                 :             : 
     797                 :           2 :         arrout = ARRPTR(tsout);
     798                 :           2 :         cur = STRPTR(tsout);
     799         [ +  + ]:          10 :         for (i = 0; i < nitems; i++)
     800                 :             :         {
     801                 :           8 :                 char       *lex = VARDATA(DatumGetPointer(dlexemes[i]));
     802                 :           8 :                 int                     lex_len = VARSIZE(DatumGetPointer(dlexemes[i])) - VARHDRSZ;
     803                 :             : 
     804                 :           8 :                 memcpy(cur, lex, lex_len);
     805                 :           8 :                 arrout[i].haspos = 0;
     806                 :           8 :                 arrout[i].len = lex_len;
     807                 :           8 :                 arrout[i].pos = cur - STRPTR(tsout);
     808                 :           8 :                 cur += lex_len;
     809                 :           8 :         }
     810                 :             : 
     811         [ -  + ]:           2 :         PG_FREE_IF_COPY(v, 0);
     812                 :           4 :         PG_RETURN_POINTER(tsout);
     813                 :           2 : }
     814                 :             : 
     815                 :             : /*
     816                 :             :  * ts_filter(): keep only lexemes with given weights in tsvector.
     817                 :             :  */
     818                 :             : Datum
     819                 :           3 : tsvector_filter(PG_FUNCTION_ARGS)
     820                 :             : {
     821                 :           3 :         TSVector        tsin = PG_GETARG_TSVECTOR(0),
     822                 :             :                                 tsout;
     823                 :           3 :         ArrayType  *weights = PG_GETARG_ARRAYTYPE_P(1);
     824                 :           3 :         WordEntry  *arrin = ARRPTR(tsin),
     825                 :             :                            *arrout;
     826                 :           3 :         char       *datain = STRPTR(tsin),
     827                 :             :                            *dataout;
     828                 :           3 :         Datum      *dweights;
     829                 :           3 :         bool       *nulls;
     830                 :           3 :         int                     nweights;
     831                 :           3 :         int                     i,
     832                 :             :                                 j;
     833                 :           3 :         int                     cur_pos = 0;
     834                 :           3 :         char            mask = 0;
     835                 :             : 
     836                 :           3 :         deconstruct_array_builtin(weights, CHAROID, &dweights, &nulls, &nweights);
     837                 :             : 
     838         [ +  + ]:           7 :         for (i = 0; i < nweights; i++)
     839                 :             :         {
     840                 :           5 :                 char            char_weight;
     841                 :             : 
     842         [ +  + ]:           5 :                 if (nulls[i])
     843   [ +  -  +  - ]:           1 :                         ereport(ERROR,
     844                 :             :                                         (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
     845                 :             :                                          errmsg("weight array may not contain nulls")));
     846                 :             : 
     847                 :           4 :                 char_weight = DatumGetChar(dweights[i]);
     848   [ +  +  -  -  :           4 :                 switch (char_weight)
                      - ]
     849                 :             :                 {
     850                 :             :                         case 'A':
     851                 :             :                         case 'a':
     852                 :           3 :                                 mask = mask | 8;
     853                 :           3 :                                 break;
     854                 :             :                         case 'B':
     855                 :             :                         case 'b':
     856                 :           1 :                                 mask = mask | 4;
     857                 :           1 :                                 break;
     858                 :             :                         case 'C':
     859                 :             :                         case 'c':
     860                 :           0 :                                 mask = mask | 2;
     861                 :           0 :                                 break;
     862                 :             :                         case 'D':
     863                 :             :                         case 'd':
     864                 :           0 :                                 mask = mask | 1;
     865                 :           0 :                                 break;
     866                 :             :                         default:
     867   [ #  #  #  # ]:           0 :                                 ereport(ERROR,
     868                 :             :                                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     869                 :             :                                                  errmsg("unrecognized weight: \"%c\"", char_weight)));
     870                 :           0 :                 }
     871                 :           4 :         }
     872                 :             : 
     873                 :           2 :         tsout = (TSVector) palloc0(VARSIZE(tsin));
     874                 :           2 :         tsout->size = tsin->size;
     875                 :           2 :         arrout = ARRPTR(tsout);
     876                 :           2 :         dataout = STRPTR(tsout);
     877                 :             : 
     878         [ +  + ]:          18 :         for (i = j = 0; i < tsin->size; i++)
     879                 :             :         {
     880                 :          16 :                 WordEntryPosVector *posvin,
     881                 :             :                                    *posvout;
     882                 :          16 :                 int                     npos = 0;
     883                 :          16 :                 int                     k;
     884                 :             : 
     885         [ +  + ]:          16 :                 if (!arrin[i].haspos)
     886                 :           5 :                         continue;
     887                 :             : 
     888                 :          11 :                 posvin = _POSVECPTR(tsin, arrin + i);
     889                 :          11 :                 posvout = (WordEntryPosVector *)
     890                 :          11 :                         (dataout + SHORTALIGN(cur_pos + arrin[i].len));
     891                 :             : 
     892         [ +  + ]:          22 :                 for (k = 0; k < posvin->npos; k++)
     893                 :             :                 {
     894         [ +  + ]:          11 :                         if (mask & (1 << WEP_GETWEIGHT(posvin->pos[k])))
     895                 :           5 :                                 posvout->pos[npos++] = posvin->pos[k];
     896                 :          11 :                 }
     897                 :             : 
     898                 :             :                 /* if no satisfactory positions found, skip lexeme */
     899         [ +  + ]:          11 :                 if (!npos)
     900                 :           6 :                         continue;
     901                 :             : 
     902                 :           5 :                 arrout[j].haspos = true;
     903                 :           5 :                 arrout[j].len = arrin[i].len;
     904                 :           5 :                 arrout[j].pos = cur_pos;
     905                 :             : 
     906                 :           5 :                 memcpy(dataout + cur_pos, datain + arrin[i].pos, arrin[i].len);
     907                 :           5 :                 posvout->npos = npos;
     908                 :           5 :                 cur_pos += SHORTALIGN(arrin[i].len);
     909         [ +  - ]:           5 :                 cur_pos += POSDATALEN(tsout, arrout + j) * sizeof(WordEntryPos) +
     910                 :             :                         sizeof(uint16);
     911                 :           5 :                 j++;
     912      [ -  +  + ]:          16 :         }
     913                 :             : 
     914                 :           2 :         tsout->size = j;
     915         [ -  + ]:           2 :         if (dataout != STRPTR(tsout))
     916                 :           2 :                 memmove(STRPTR(tsout), dataout, cur_pos);
     917                 :             : 
     918                 :           2 :         SET_VARSIZE(tsout, CALCDATASIZE(tsout->size, cur_pos));
     919                 :             : 
     920         [ +  - ]:           2 :         PG_FREE_IF_COPY(tsin, 0);
     921                 :           4 :         PG_RETURN_POINTER(tsout);
     922                 :           2 : }
     923                 :             : 
     924                 :             : Datum
     925                 :           2 : tsvector_concat(PG_FUNCTION_ARGS)
     926                 :             : {
     927                 :           2 :         TSVector        in1 = PG_GETARG_TSVECTOR(0);
     928                 :           2 :         TSVector        in2 = PG_GETARG_TSVECTOR(1);
     929                 :           2 :         TSVector        out;
     930                 :           2 :         WordEntry  *ptr;
     931                 :           2 :         WordEntry  *ptr1,
     932                 :             :                            *ptr2;
     933                 :           2 :         WordEntryPos *p;
     934                 :           2 :         int                     maxpos = 0,
     935                 :             :                                 i,
     936                 :             :                                 j,
     937                 :             :                                 i1,
     938                 :             :                                 i2,
     939                 :             :                                 dataoff,
     940                 :             :                                 output_bytes,
     941                 :             :                                 output_size;
     942                 :           2 :         char       *data,
     943                 :             :                            *data1,
     944                 :             :                            *data2;
     945                 :             : 
     946                 :             :         /* Get max position in in1; we'll need this to offset in2's positions */
     947                 :           2 :         ptr = ARRPTR(in1);
     948                 :           2 :         i = in1->size;
     949         [ +  + ]:           5 :         while (i--)
     950                 :             :         {
     951   [ +  -  +  - ]:           3 :                 if ((j = POSDATALEN(in1, ptr)) != 0)
     952                 :             :                 {
     953                 :           3 :                         p = POSDATAPTR(in1, ptr);
     954         [ +  + ]:           6 :                         while (j--)
     955                 :             :                         {
     956         [ +  + ]:           3 :                                 if (WEP_GETPOS(*p) > maxpos)
     957                 :           2 :                                         maxpos = WEP_GETPOS(*p);
     958                 :           3 :                                 p++;
     959                 :             :                         }
     960                 :           3 :                 }
     961                 :           3 :                 ptr++;
     962                 :             :         }
     963                 :             : 
     964                 :           2 :         ptr1 = ARRPTR(in1);
     965                 :           2 :         ptr2 = ARRPTR(in2);
     966                 :           2 :         data1 = STRPTR(in1);
     967                 :           2 :         data2 = STRPTR(in2);
     968                 :           2 :         i1 = in1->size;
     969                 :           2 :         i2 = in2->size;
     970                 :             : 
     971                 :             :         /*
     972                 :             :          * Conservative estimate of space needed.  We might need all the data in
     973                 :             :          * both inputs, and conceivably add a pad byte before position data for
     974                 :             :          * each item where there was none before.
     975                 :             :          */
     976                 :           2 :         output_bytes = VARSIZE(in1) + VARSIZE(in2) + i1 + i2;
     977                 :             : 
     978                 :           2 :         out = (TSVector) palloc0(output_bytes);
     979                 :           2 :         SET_VARSIZE(out, output_bytes);
     980                 :             : 
     981                 :             :         /*
     982                 :             :          * We must make out->size valid so that STRPTR(out) is sensible.  We'll
     983                 :             :          * collapse out any unused space at the end.
     984                 :             :          */
     985                 :           2 :         out->size = in1->size + in2->size;
     986                 :             : 
     987                 :           2 :         ptr = ARRPTR(out);
     988                 :           2 :         data = STRPTR(out);
     989                 :           2 :         dataoff = 0;
     990   [ +  +  +  + ]:           5 :         while (i1 && i2)
     991                 :             :         {
     992                 :           3 :                 int                     cmp = compareEntry(data1, ptr1, data2, ptr2);
     993                 :             : 
     994         [ +  + ]:           3 :                 if (cmp < 0)
     995                 :             :                 {                                               /* in1 first */
     996                 :           1 :                         ptr->haspos = ptr1->haspos;
     997                 :           1 :                         ptr->len = ptr1->len;
     998                 :           1 :                         memcpy(data + dataoff, data1 + ptr1->pos, ptr1->len);
     999                 :           1 :                         ptr->pos = dataoff;
    1000                 :           1 :                         dataoff += ptr1->len;
    1001         [ -  + ]:           1 :                         if (ptr->haspos)
    1002                 :             :                         {
    1003                 :           1 :                                 dataoff = SHORTALIGN(dataoff);
    1004         [ +  - ]:           1 :                                 memcpy(data + dataoff, _POSVECPTR(in1, ptr1), POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16));
    1005         [ +  - ]:           1 :                                 dataoff += POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16);
    1006                 :           1 :                         }
    1007                 :             : 
    1008                 :           1 :                         ptr++;
    1009                 :           1 :                         ptr1++;
    1010                 :           1 :                         i1--;
    1011                 :           1 :                 }
    1012         [ +  + ]:           2 :                 else if (cmp > 0)
    1013                 :             :                 {                                               /* in2 first */
    1014                 :           1 :                         ptr->haspos = ptr2->haspos;
    1015                 :           1 :                         ptr->len = ptr2->len;
    1016                 :           1 :                         memcpy(data + dataoff, data2 + ptr2->pos, ptr2->len);
    1017                 :           1 :                         ptr->pos = dataoff;
    1018                 :           1 :                         dataoff += ptr2->len;
    1019         [ +  - ]:           1 :                         if (ptr->haspos)
    1020                 :             :                         {
    1021                 :           0 :                                 int                     addlen = add_pos(in2, ptr2, out, ptr, maxpos);
    1022                 :             : 
    1023         [ #  # ]:           0 :                                 if (addlen == 0)
    1024                 :           0 :                                         ptr->haspos = 0;
    1025                 :             :                                 else
    1026                 :             :                                 {
    1027                 :           0 :                                         dataoff = SHORTALIGN(dataoff);
    1028                 :           0 :                                         dataoff += addlen * sizeof(WordEntryPos) + sizeof(uint16);
    1029                 :             :                                 }
    1030                 :           0 :                         }
    1031                 :             : 
    1032                 :           1 :                         ptr++;
    1033                 :           1 :                         ptr2++;
    1034                 :           1 :                         i2--;
    1035                 :           1 :                 }
    1036                 :             :                 else
    1037                 :             :                 {
    1038                 :           1 :                         ptr->haspos = ptr1->haspos | ptr2->haspos;
    1039                 :           1 :                         ptr->len = ptr1->len;
    1040                 :           1 :                         memcpy(data + dataoff, data1 + ptr1->pos, ptr1->len);
    1041                 :           1 :                         ptr->pos = dataoff;
    1042                 :           1 :                         dataoff += ptr1->len;
    1043         [ -  + ]:           1 :                         if (ptr->haspos)
    1044                 :             :                         {
    1045         [ +  - ]:           1 :                                 if (ptr1->haspos)
    1046                 :             :                                 {
    1047                 :           1 :                                         dataoff = SHORTALIGN(dataoff);
    1048         [ +  - ]:           1 :                                         memcpy(data + dataoff, _POSVECPTR(in1, ptr1), POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16));
    1049         [ +  - ]:           1 :                                         dataoff += POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16);
    1050         [ -  + ]:           1 :                                         if (ptr2->haspos)
    1051                 :           1 :                                                 dataoff += add_pos(in2, ptr2, out, ptr, maxpos) * sizeof(WordEntryPos);
    1052                 :           1 :                                 }
    1053                 :             :                                 else                    /* must have ptr2->haspos */
    1054                 :             :                                 {
    1055                 :           0 :                                         int                     addlen = add_pos(in2, ptr2, out, ptr, maxpos);
    1056                 :             : 
    1057         [ #  # ]:           0 :                                         if (addlen == 0)
    1058                 :           0 :                                                 ptr->haspos = 0;
    1059                 :             :                                         else
    1060                 :             :                                         {
    1061                 :           0 :                                                 dataoff = SHORTALIGN(dataoff);
    1062                 :           0 :                                                 dataoff += addlen * sizeof(WordEntryPos) + sizeof(uint16);
    1063                 :             :                                         }
    1064                 :           0 :                                 }
    1065                 :           1 :                         }
    1066                 :             : 
    1067                 :           1 :                         ptr++;
    1068                 :           1 :                         ptr1++;
    1069                 :           1 :                         ptr2++;
    1070                 :           1 :                         i1--;
    1071                 :           1 :                         i2--;
    1072                 :             :                 }
    1073                 :           3 :         }
    1074                 :             : 
    1075         [ +  + ]:           3 :         while (i1)
    1076                 :             :         {
    1077                 :           1 :                 ptr->haspos = ptr1->haspos;
    1078                 :           1 :                 ptr->len = ptr1->len;
    1079                 :           1 :                 memcpy(data + dataoff, data1 + ptr1->pos, ptr1->len);
    1080                 :           1 :                 ptr->pos = dataoff;
    1081                 :           1 :                 dataoff += ptr1->len;
    1082         [ -  + ]:           1 :                 if (ptr->haspos)
    1083                 :             :                 {
    1084                 :           1 :                         dataoff = SHORTALIGN(dataoff);
    1085         [ +  - ]:           1 :                         memcpy(data + dataoff, _POSVECPTR(in1, ptr1), POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16));
    1086         [ +  - ]:           1 :                         dataoff += POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16);
    1087                 :           1 :                 }
    1088                 :             : 
    1089                 :           1 :                 ptr++;
    1090                 :           1 :                 ptr1++;
    1091                 :           1 :                 i1--;
    1092                 :             :         }
    1093                 :             : 
    1094         [ +  + ]:           3 :         while (i2)
    1095                 :             :         {
    1096                 :           1 :                 ptr->haspos = ptr2->haspos;
    1097                 :           1 :                 ptr->len = ptr2->len;
    1098                 :           1 :                 memcpy(data + dataoff, data2 + ptr2->pos, ptr2->len);
    1099                 :           1 :                 ptr->pos = dataoff;
    1100                 :           1 :                 dataoff += ptr2->len;
    1101         [ -  + ]:           1 :                 if (ptr->haspos)
    1102                 :             :                 {
    1103                 :           1 :                         int                     addlen = add_pos(in2, ptr2, out, ptr, maxpos);
    1104                 :             : 
    1105         [ +  - ]:           1 :                         if (addlen == 0)
    1106                 :           0 :                                 ptr->haspos = 0;
    1107                 :             :                         else
    1108                 :             :                         {
    1109                 :           1 :                                 dataoff = SHORTALIGN(dataoff);
    1110                 :           1 :                                 dataoff += addlen * sizeof(WordEntryPos) + sizeof(uint16);
    1111                 :             :                         }
    1112                 :           1 :                 }
    1113                 :             : 
    1114                 :           1 :                 ptr++;
    1115                 :           1 :                 ptr2++;
    1116                 :           1 :                 i2--;
    1117                 :             :         }
    1118                 :             : 
    1119                 :             :         /*
    1120                 :             :          * Instead of checking each offset individually, we check for overflow of
    1121                 :             :          * pos fields once at the end.
    1122                 :             :          */
    1123         [ +  - ]:           2 :         if (dataoff > MAXSTRPOS)
    1124   [ #  #  #  # ]:           0 :                 ereport(ERROR,
    1125                 :             :                                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
    1126                 :             :                                  errmsg("string is too long for tsvector (%d bytes, max %d bytes)", dataoff, MAXSTRPOS)));
    1127                 :             : 
    1128                 :             :         /*
    1129                 :             :          * Adjust sizes (asserting that we didn't overrun the original estimates)
    1130                 :             :          * and collapse out any unused array entries.
    1131                 :             :          */
    1132                 :           2 :         output_size = ptr - ARRPTR(out);
    1133         [ +  - ]:           2 :         Assert(output_size <= out->size);
    1134                 :           2 :         out->size = output_size;
    1135         [ +  + ]:           2 :         if (data != STRPTR(out))
    1136                 :           1 :                 memmove(STRPTR(out), data, dataoff);
    1137                 :           2 :         output_bytes = CALCDATASIZE(out->size, dataoff);
    1138         [ +  - ]:           2 :         Assert(output_bytes <= VARSIZE(out));
    1139                 :           2 :         SET_VARSIZE(out, output_bytes);
    1140                 :             : 
    1141         [ +  - ]:           2 :         PG_FREE_IF_COPY(in1, 0);
    1142         [ +  - ]:           2 :         PG_FREE_IF_COPY(in2, 1);
    1143                 :           4 :         PG_RETURN_POINTER(out);
    1144                 :           2 : }
    1145                 :             : 
    1146                 :             : /*
    1147                 :             :  * Compare two strings by tsvector rules.
    1148                 :             :  *
    1149                 :             :  * if prefix = true then it returns zero value iff b has prefix a
    1150                 :             :  */
    1151                 :             : int32
    1152                 :     1035888 : tsCompareString(char *a, int lena, char *b, int lenb, bool prefix)
    1153                 :             : {
    1154                 :     1035888 :         int                     cmp;
    1155                 :             : 
    1156         [ +  + ]:     1035888 :         if (lena == 0)
    1157                 :             :         {
    1158         [ -  + ]:           6 :                 if (prefix)
    1159                 :           0 :                         cmp = 0;                        /* empty string is prefix of anything */
    1160                 :             :                 else
    1161                 :           6 :                         cmp = (lenb > 0) ? -1 : 0;
    1162                 :           6 :         }
    1163         [ +  - ]:     1035882 :         else if (lenb == 0)
    1164                 :             :         {
    1165                 :           0 :                 cmp = (lena > 0) ? 1 : 0;
    1166                 :           0 :         }
    1167                 :             :         else
    1168                 :             :         {
    1169         [ +  + ]:     1035882 :                 cmp = memcmp(a, b, Min((unsigned int) lena, (unsigned int) lenb));
    1170                 :             : 
    1171         [ +  + ]:     1035882 :                 if (prefix)
    1172                 :             :                 {
    1173   [ +  +  +  - ]:        2743 :                         if (cmp == 0 && lena > lenb)
    1174                 :           0 :                                 cmp = 1;                /* a is longer, so not a prefix of b */
    1175                 :        2743 :                 }
    1176   [ +  +  +  + ]:     1033139 :                 else if (cmp == 0 && lena != lenb)
    1177                 :             :                 {
    1178                 :        5378 :                         cmp = (lena < lenb) ? -1 : 1;
    1179                 :        5378 :                 }
    1180                 :             :         }
    1181                 :             : 
    1182                 :     2071776 :         return cmp;
    1183                 :     1035888 : }
    1184                 :             : 
    1185                 :             : /*
    1186                 :             :  * Check weight info or/and fill 'data' with the required positions
    1187                 :             :  */
    1188                 :             : static TSTernaryValue
    1189                 :       11347 : checkclass_str(CHKVAL *chkval, WordEntry *entry, QueryOperand *val,
    1190                 :             :                            ExecPhraseData *data)
    1191                 :             : {
    1192                 :       11347 :         TSTernaryValue result = TS_NO;
    1193                 :             : 
    1194   [ +  +  +  - ]:       11347 :         Assert(data == NULL || data->npos == 0);
    1195                 :             : 
    1196         [ +  + ]:       11347 :         if (entry->haspos)
    1197                 :             :         {
    1198                 :         748 :                 WordEntryPosVector *posvec;
    1199                 :             : 
    1200                 :             :                 /*
    1201                 :             :                  * We can't use the _POSVECPTR macro here because the pointer to the
    1202                 :             :                  * tsvector's lexeme storage is already contained in chkval->values.
    1203                 :             :                  */
    1204                 :         748 :                 posvec = (WordEntryPosVector *)
    1205                 :         748 :                         (chkval->values + SHORTALIGN(entry->pos + entry->len));
    1206                 :             : 
    1207   [ +  +  +  + ]:         748 :                 if (val->weight && data)
    1208                 :             :                 {
    1209                 :           8 :                         WordEntryPos *posvec_iter = posvec->pos;
    1210                 :           8 :                         WordEntryPos *dptr;
    1211                 :             : 
    1212                 :             :                         /*
    1213                 :             :                          * Filter position information by weights
    1214                 :             :                          */
    1215                 :           8 :                         dptr = data->pos = palloc_array(WordEntryPos, posvec->npos);
    1216                 :           8 :                         data->allocated = true;
    1217                 :             : 
    1218                 :             :                         /* Is there a position with a matching weight? */
    1219         [ +  + ]:          16 :                         while (posvec_iter < posvec->pos + posvec->npos)
    1220                 :             :                         {
    1221                 :             :                                 /* If true, append this position to the data->pos */
    1222         [ +  + ]:           8 :                                 if (val->weight & (1 << WEP_GETWEIGHT(*posvec_iter)))
    1223                 :             :                                 {
    1224                 :           4 :                                         *dptr = WEP_GETPOS(*posvec_iter);
    1225                 :           4 :                                         dptr++;
    1226                 :           4 :                                 }
    1227                 :             : 
    1228                 :           8 :                                 posvec_iter++;
    1229                 :             :                         }
    1230                 :             : 
    1231                 :           8 :                         data->npos = dptr - data->pos;
    1232                 :             : 
    1233         [ +  + ]:           8 :                         if (data->npos > 0)
    1234                 :           4 :                                 result = TS_YES;
    1235                 :             :                         else
    1236                 :             :                         {
    1237                 :           4 :                                 pfree(data->pos);
    1238                 :           4 :                                 data->pos = NULL;
    1239                 :           4 :                                 data->allocated = false;
    1240                 :             :                         }
    1241                 :           8 :                 }
    1242         [ +  + ]:         740 :                 else if (val->weight)
    1243                 :             :                 {
    1244                 :          76 :                         WordEntryPos *posvec_iter = posvec->pos;
    1245                 :             : 
    1246                 :             :                         /* Is there a position with a matching weight? */
    1247         [ +  + ]:         115 :                         while (posvec_iter < posvec->pos + posvec->npos)
    1248                 :             :                         {
    1249         [ +  + ]:          84 :                                 if (val->weight & (1 << WEP_GETWEIGHT(*posvec_iter)))
    1250                 :             :                                 {
    1251                 :          45 :                                         result = TS_YES;
    1252                 :          45 :                                         break;          /* no need to go further */
    1253                 :             :                                 }
    1254                 :             : 
    1255                 :          39 :                                 posvec_iter++;
    1256                 :             :                         }
    1257                 :          76 :                 }
    1258         [ +  + ]:         664 :                 else if (data)
    1259                 :             :                 {
    1260                 :         379 :                         data->npos = posvec->npos;
    1261                 :         379 :                         data->pos = posvec->pos;
    1262                 :         379 :                         data->allocated = false;
    1263                 :         379 :                         result = TS_YES;
    1264                 :         379 :                 }
    1265                 :             :                 else
    1266                 :             :                 {
    1267                 :             :                         /* simplest case: no weight check, positions not needed */
    1268                 :         285 :                         result = TS_YES;
    1269                 :             :                 }
    1270                 :         748 :         }
    1271                 :             :         else
    1272                 :             :         {
    1273                 :             :                 /*
    1274                 :             :                  * Position info is lacking, so if the caller requires it, we can only
    1275                 :             :                  * say that maybe there is a match.
    1276                 :             :                  *
    1277                 :             :                  * Notice, however, that we *don't* check val->weight here.
    1278                 :             :                  * Historically, stripped tsvectors are considered to match queries
    1279                 :             :                  * whether or not the query has a weight restriction; that's a little
    1280                 :             :                  * dubious but we'll preserve the behavior.
    1281                 :             :                  */
    1282         [ +  + ]:       10599 :                 if (data)
    1283                 :        3843 :                         result = TS_MAYBE;
    1284                 :             :                 else
    1285                 :        6756 :                         result = TS_YES;
    1286                 :             :         }
    1287                 :             : 
    1288                 :       22694 :         return result;
    1289                 :       11347 : }
    1290                 :             : 
    1291                 :             : /*
    1292                 :             :  * TS_execute callback for matching a tsquery operand to plain tsvector data
    1293                 :             :  */
    1294                 :             : static TSTernaryValue
    1295                 :       47337 : checkcondition_str(void *checkval, QueryOperand *val, ExecPhraseData *data)
    1296                 :             : {
    1297                 :       47337 :         CHKVAL     *chkval = (CHKVAL *) checkval;
    1298                 :       47337 :         WordEntry  *StopLow = chkval->arrb;
    1299                 :       47337 :         WordEntry  *StopHigh = chkval->arre;
    1300                 :       47337 :         WordEntry  *StopMiddle = StopHigh;
    1301                 :       47337 :         TSTernaryValue res = TS_NO;
    1302                 :             : 
    1303                 :             :         /* Loop invariant: StopLow <= val < StopHigh */
    1304         [ +  + ]:      297801 :         while (StopLow < StopHigh)
    1305                 :             :         {
    1306                 :      259305 :                 int                     difference;
    1307                 :             : 
    1308                 :      259305 :                 StopMiddle = StopLow + (StopHigh - StopLow) / 2;
    1309                 :      518610 :                 difference = tsCompareString(chkval->operand + val->distance,
    1310                 :      259305 :                                                                          val->length,
    1311                 :      259305 :                                                                          chkval->values + StopMiddle->pos,
    1312                 :      259305 :                                                                          StopMiddle->len,
    1313                 :             :                                                                          false);
    1314                 :             : 
    1315         [ +  + ]:      259305 :                 if (difference == 0)
    1316                 :             :                 {
    1317                 :             :                         /* Check weight info & fill 'data' with positions */
    1318                 :        8841 :                         res = checkclass_str(chkval, StopMiddle, val, data);
    1319                 :        8841 :                         break;
    1320                 :             :                 }
    1321         [ +  + ]:      250464 :                 else if (difference > 0)
    1322                 :      141252 :                         StopLow = StopMiddle + 1;
    1323                 :             :                 else
    1324                 :      109212 :                         StopHigh = StopMiddle;
    1325         [ +  + ]:      259305 :         }
    1326                 :             : 
    1327                 :             :         /*
    1328                 :             :          * If it's a prefix search, we should also consider lexemes that the
    1329                 :             :          * search term is a prefix of (which will necessarily immediately follow
    1330                 :             :          * the place we found in the above loop).  But we can skip them if there
    1331                 :             :          * was a definite match on the exact term AND the caller doesn't need
    1332                 :             :          * position info.
    1333                 :             :          */
    1334   [ +  +  +  +  :       47337 :         if (val->prefix && (res != TS_YES || data))
                   +  - ]
    1335                 :             :         {
    1336                 :        2754 :                 WordEntryPos *allpos = NULL;
    1337                 :        2754 :                 int                     npos = 0,
    1338                 :        2754 :                                         totalpos = 0;
    1339                 :             : 
    1340                 :             :                 /* adjust start position for corner case */
    1341         [ +  + ]:        2754 :                 if (StopLow >= StopHigh)
    1342                 :        2752 :                         StopMiddle = StopHigh;
    1343                 :             : 
    1344                 :             :                 /* we don't try to re-use any data from the initial match */
    1345         [ +  + ]:        2754 :                 if (data)
    1346                 :             :                 {
    1347         [ +  - ]:           6 :                         if (data->allocated)
    1348                 :           0 :                                 pfree(data->pos);
    1349                 :           6 :                         data->pos = NULL;
    1350                 :           6 :                         data->allocated = false;
    1351                 :           6 :                         data->npos = 0;
    1352                 :           6 :                 }
    1353                 :        2754 :                 res = TS_NO;
    1354                 :             : 
    1355   [ +  +  +  + ]:       10520 :                 while ((res != TS_YES || data) &&
    1356         [ +  + ]:        5260 :                            StopMiddle < chkval->arre &&
    1357                 :        5310 :                            tsCompareString(chkval->operand + val->distance,
    1358                 :        2655 :                                                            val->length,
    1359                 :        2655 :                                                            chkval->values + StopMiddle->pos,
    1360                 :        2655 :                                                            StopMiddle->len,
    1361                 :        2655 :                                                            true) == 0)
    1362                 :             :                 {
    1363                 :        2506 :                         TSTernaryValue subres;
    1364                 :             : 
    1365                 :        2506 :                         subres = checkclass_str(chkval, StopMiddle, val, data);
    1366                 :             : 
    1367         [ +  + ]:        2506 :                         if (subres != TS_NO)
    1368                 :             :                         {
    1369         [ +  + ]:        2496 :                                 if (data)
    1370                 :             :                                 {
    1371                 :             :                                         /*
    1372                 :             :                                          * We need to join position information
    1373                 :             :                                          */
    1374         [ -  + ]:           7 :                                         if (subres == TS_MAYBE)
    1375                 :             :                                         {
    1376                 :             :                                                 /*
    1377                 :             :                                                  * No position info for this match, so we must report
    1378                 :             :                                                  * MAYBE overall.
    1379                 :             :                                                  */
    1380                 :           0 :                                                 res = TS_MAYBE;
    1381                 :             :                                                 /* forget any previous positions */
    1382                 :           0 :                                                 npos = 0;
    1383                 :             :                                                 /* don't leak storage */
    1384         [ #  # ]:           0 :                                                 if (allpos)
    1385                 :           0 :                                                         pfree(allpos);
    1386                 :           0 :                                                 break;
    1387                 :             :                                         }
    1388                 :             : 
    1389         [ +  + ]:          13 :                                         while (npos + data->npos > totalpos)
    1390                 :             :                                         {
    1391         [ -  + ]:           6 :                                                 if (totalpos == 0)
    1392                 :             :                                                 {
    1393                 :           6 :                                                         totalpos = 256;
    1394                 :           6 :                                                         allpos = palloc_array(WordEntryPos, totalpos);
    1395                 :           6 :                                                 }
    1396                 :             :                                                 else
    1397                 :             :                                                 {
    1398                 :           0 :                                                         totalpos *= 2;
    1399                 :           0 :                                                         allpos = repalloc_array(allpos, WordEntryPos, totalpos);
    1400                 :             :                                                 }
    1401                 :             :                                         }
    1402                 :             : 
    1403                 :           7 :                                         memcpy(allpos + npos, data->pos, sizeof(WordEntryPos) * data->npos);
    1404                 :           7 :                                         npos += data->npos;
    1405                 :             : 
    1406                 :             :                                         /* don't leak storage from individual matches */
    1407         [ +  + ]:           7 :                                         if (data->allocated)
    1408                 :           4 :                                                 pfree(data->pos);
    1409                 :           7 :                                         data->pos = NULL;
    1410                 :           7 :                                         data->allocated = false;
    1411                 :             :                                         /* it's important to reset data->npos before next loop */
    1412                 :           7 :                                         data->npos = 0;
    1413                 :           7 :                                 }
    1414                 :             :                                 else
    1415                 :             :                                 {
    1416                 :             :                                         /* Don't need positions, just handle YES/MAYBE */
    1417   [ -  +  #  # ]:        2489 :                                         if (subres == TS_YES || res == TS_NO)
    1418                 :        2489 :                                                 res = subres;
    1419                 :             :                                 }
    1420                 :        2496 :                         }
    1421                 :             : 
    1422                 :        2506 :                         StopMiddle++;
    1423         [ -  + ]:        2506 :                 }
    1424                 :             : 
    1425   [ +  +  -  + ]:        2754 :                 if (data && npos > 0)
    1426                 :             :                 {
    1427                 :             :                         /* Sort and make unique array of found positions */
    1428                 :           6 :                         data->pos = allpos;
    1429                 :           6 :                         qsort(data->pos, npos, sizeof(WordEntryPos), compareWordEntryPos);
    1430                 :           6 :                         data->npos = qunique(data->pos, npos, sizeof(WordEntryPos),
    1431                 :             :                                                                  compareWordEntryPos);
    1432                 :           6 :                         data->allocated = true;
    1433                 :           6 :                         res = TS_YES;
    1434                 :           6 :                 }
    1435                 :        2754 :         }
    1436                 :             : 
    1437                 :       94674 :         return res;
    1438                 :       47337 : }
    1439                 :             : 
    1440                 :             : /*
    1441                 :             :  * Compute output position list for a tsquery operator in phrase mode.
    1442                 :             :  *
    1443                 :             :  * Merge the position lists in Ldata and Rdata as specified by "emit",
    1444                 :             :  * returning the result list into *data.  The input position lists must be
    1445                 :             :  * sorted and unique, and the output will be as well.
    1446                 :             :  *
    1447                 :             :  * data: pointer to initially-all-zeroes output struct, or NULL
    1448                 :             :  * Ldata, Rdata: input position lists
    1449                 :             :  * emit: bitmask of TSPO_XXX flags
    1450                 :             :  * Loffset: offset to be added to Ldata positions before comparing/outputting
    1451                 :             :  * Roffset: offset to be added to Rdata positions before comparing/outputting
    1452                 :             :  * max_npos: maximum possible required size of output position array
    1453                 :             :  *
    1454                 :             :  * Loffset and Roffset should not be negative, else we risk trying to output
    1455                 :             :  * negative positions, which won't fit into WordEntryPos.
    1456                 :             :  *
    1457                 :             :  * The result is boolean (TS_YES or TS_NO), but for the caller's convenience
    1458                 :             :  * we return it as TSTernaryValue.
    1459                 :             :  *
    1460                 :             :  * Returns TS_YES if any positions were emitted to *data; or if data is NULL,
    1461                 :             :  * returns TS_YES if any positions would have been emitted.
    1462                 :             :  */
    1463                 :             : #define TSPO_L_ONLY             0x01    /* emit positions appearing only in L */
    1464                 :             : #define TSPO_R_ONLY             0x02    /* emit positions appearing only in R */
    1465                 :             : #define TSPO_BOTH               0x04    /* emit positions appearing in both L&R */
    1466                 :             : 
    1467                 :             : static TSTernaryValue
    1468                 :        4994 : TS_phrase_output(ExecPhraseData *data,
    1469                 :             :                                  ExecPhraseData *Ldata,
    1470                 :             :                                  ExecPhraseData *Rdata,
    1471                 :             :                                  int emit,
    1472                 :             :                                  int Loffset,
    1473                 :             :                                  int Roffset,
    1474                 :             :                                  int max_npos)
    1475                 :             : {
    1476                 :        4994 :         int                     Lindex,
    1477                 :             :                                 Rindex;
    1478                 :             : 
    1479                 :             :         /* Loop until both inputs are exhausted */
    1480                 :        4994 :         Lindex = Rindex = 0;
    1481   [ +  +  +  + ]:        5166 :         while (Lindex < Ldata->npos || Rindex < Rdata->npos)
    1482                 :             :         {
    1483                 :         389 :                 int                     Lpos,
    1484                 :             :                                         Rpos;
    1485                 :         389 :                 int                     output_pos = 0;
    1486                 :             : 
    1487                 :             :                 /*
    1488                 :             :                  * Fetch current values to compare.  WEP_GETPOS() is needed because
    1489                 :             :                  * ExecPhraseData->data can point to a tsvector's WordEntryPosVector.
    1490                 :             :                  */
    1491         [ +  + ]:         389 :                 if (Lindex < Ldata->npos)
    1492                 :         281 :                         Lpos = WEP_GETPOS(Ldata->pos[Lindex]) + Loffset;
    1493                 :             :                 else
    1494                 :             :                 {
    1495                 :             :                         /* L array exhausted, so we're done if R_ONLY isn't set */
    1496         [ +  + ]:         108 :                         if (!(emit & TSPO_R_ONLY))
    1497                 :          25 :                                 break;
    1498                 :          83 :                         Lpos = INT_MAX;
    1499                 :             :                 }
    1500         [ +  + ]:         364 :                 if (Rindex < Rdata->npos)
    1501                 :         323 :                         Rpos = WEP_GETPOS(Rdata->pos[Rindex]) + Roffset;
    1502                 :             :                 else
    1503                 :             :                 {
    1504                 :             :                         /* R array exhausted, so we're done if L_ONLY isn't set */
    1505         [ +  + ]:          41 :                         if (!(emit & TSPO_L_ONLY))
    1506                 :          27 :                                 break;
    1507                 :          14 :                         Rpos = INT_MAX;
    1508                 :             :                 }
    1509                 :             : 
    1510                 :             :                 /* Merge-join the two input lists */
    1511         [ +  + ]:         337 :                 if (Lpos < Rpos)
    1512                 :             :                 {
    1513                 :             :                         /* Lpos is not matched in Rdata, should we output it? */
    1514         [ +  + ]:          81 :                         if (emit & TSPO_L_ONLY)
    1515                 :          24 :                                 output_pos = Lpos;
    1516                 :          81 :                         Lindex++;
    1517                 :          81 :                 }
    1518         [ +  + ]:         256 :                 else if (Lpos == Rpos)
    1519                 :             :                 {
    1520                 :             :                         /* Lpos and Rpos match ... should we output it? */
    1521         [ +  + ]:         133 :                         if (emit & TSPO_BOTH)
    1522                 :         117 :                                 output_pos = Rpos;
    1523                 :         133 :                         Lindex++;
    1524                 :         133 :                         Rindex++;
    1525                 :         133 :                 }
    1526                 :             :                 else                                    /* Lpos > Rpos */
    1527                 :             :                 {
    1528                 :             :                         /* Rpos is not matched in Ldata, should we output it? */
    1529         [ +  + ]:         123 :                         if (emit & TSPO_R_ONLY)
    1530                 :          90 :                                 output_pos = Rpos;
    1531                 :         123 :                         Rindex++;
    1532                 :             :                 }
    1533                 :             : 
    1534         [ +  + ]:         337 :                 if (output_pos > 0)
    1535                 :             :                 {
    1536         [ +  + ]:         231 :                         if (data)
    1537                 :             :                         {
    1538                 :             :                                 /* Store position, first allocating output array if needed */
    1539         [ +  + ]:          66 :                                 if (data->pos == NULL)
    1540                 :             :                                 {
    1541                 :          53 :                                         data->pos = (WordEntryPos *)
    1542                 :          53 :                                                 palloc(max_npos * sizeof(WordEntryPos));
    1543                 :          53 :                                         data->allocated = true;
    1544                 :          53 :                                 }
    1545                 :          66 :                                 data->pos[data->npos++] = output_pos;
    1546                 :          66 :                         }
    1547                 :             :                         else
    1548                 :             :                         {
    1549                 :             :                                 /*
    1550                 :             :                                  * Exact positions not needed, so return TS_YES as soon as we
    1551                 :             :                                  * know there is at least one.
    1552                 :             :                                  */
    1553                 :         165 :                                 return TS_YES;
    1554                 :             :                         }
    1555                 :          66 :                 }
    1556      [ +  +  + ]:         389 :         }
    1557                 :             : 
    1558   [ +  +  +  + ]:        4829 :         if (data && data->npos > 0)
    1559                 :             :         {
    1560                 :             :                 /* Let's assert we didn't overrun the array */
    1561         [ +  - ]:          53 :                 Assert(data->npos <= max_npos);
    1562                 :          53 :                 return TS_YES;
    1563                 :             :         }
    1564                 :        4776 :         return TS_NO;
    1565                 :        4994 : }
    1566                 :             : 
    1567                 :             : /*
    1568                 :             :  * Execute tsquery at or below an OP_PHRASE operator.
    1569                 :             :  *
    1570                 :             :  * This handles tsquery execution at recursion levels where we need to care
    1571                 :             :  * about match locations.
    1572                 :             :  *
    1573                 :             :  * In addition to the same arguments used for TS_execute, the caller may pass
    1574                 :             :  * a preinitialized-to-zeroes ExecPhraseData struct, to be filled with lexeme
    1575                 :             :  * match position info on success.  data == NULL if no position data need be
    1576                 :             :  * returned.
    1577                 :             :  * Note: the function assumes data != NULL for operators other than OP_PHRASE.
    1578                 :             :  * This is OK because an outside call always starts from an OP_PHRASE node,
    1579                 :             :  * and all internal recursion cases pass data != NULL.
    1580                 :             :  *
    1581                 :             :  * The detailed semantics of the match data, given that the function returned
    1582                 :             :  * TS_YES (successful match), are:
    1583                 :             :  *
    1584                 :             :  * npos > 0, negate = false:
    1585                 :             :  *       query is matched at specified position(s) (and only those positions)
    1586                 :             :  * npos > 0, negate = true:
    1587                 :             :  *       query is matched at all positions *except* specified position(s)
    1588                 :             :  * npos = 0, negate = true:
    1589                 :             :  *       query is matched at all positions
    1590                 :             :  * npos = 0, negate = false:
    1591                 :             :  *       disallowed (this should result in TS_NO or TS_MAYBE, as appropriate)
    1592                 :             :  *
    1593                 :             :  * Successful matches also return a "width" value which is the match width in
    1594                 :             :  * lexemes, less one.  Hence, "width" is zero for simple one-lexeme matches,
    1595                 :             :  * and is the sum of the phrase operator distances for phrase matches.  Note
    1596                 :             :  * that when width > 0, the listed positions represent the ends of matches not
    1597                 :             :  * the starts.  (This unintuitive rule is needed to avoid possibly generating
    1598                 :             :  * negative positions, which wouldn't fit into the WordEntryPos arrays.)
    1599                 :             :  *
    1600                 :             :  * If the TSExecuteCallback function reports that an operand is present
    1601                 :             :  * but fails to provide position(s) for it, we will return TS_MAYBE when
    1602                 :             :  * it is possible but not certain that the query is matched.
    1603                 :             :  *
    1604                 :             :  * When the function returns TS_NO or TS_MAYBE, it must return npos = 0,
    1605                 :             :  * negate = false (which is the state initialized by the caller); but the
    1606                 :             :  * "width" output in such cases is undefined.
    1607                 :             :  */
    1608                 :             : static TSTernaryValue
    1609                 :      117433 : TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
    1610                 :             :                                   TSExecuteCallback chkcond,
    1611                 :             :                                   ExecPhraseData *data)
    1612                 :             : {
    1613                 :      117433 :         ExecPhraseData Ldata,
    1614                 :             :                                 Rdata;
    1615                 :      117433 :         TSTernaryValue lmatch,
    1616                 :             :                                 rmatch;
    1617                 :      117433 :         int                     Loffset,
    1618                 :             :                                 Roffset,
    1619                 :             :                                 maxwidth;
    1620                 :             : 
    1621                 :             :         /* since this function recurses, it could be driven to stack overflow */
    1622                 :      117433 :         check_stack_depth();
    1623                 :             : 
    1624                 :             :         /* ... and let's check for query cancel while we're at it */
    1625         [ +  - ]:      117433 :         CHECK_FOR_INTERRUPTS();
    1626                 :             : 
    1627         [ +  + ]:      117433 :         if (curitem->type == QI_VAL)
    1628                 :       57769 :                 return chkcond(arg, (QueryOperand *) curitem, data);
    1629                 :             : 
    1630   [ +  +  +  - ]:       59664 :         switch (curitem->qoperator.oper)
    1631                 :             :         {
    1632                 :             :                 case OP_NOT:
    1633                 :             : 
    1634                 :             :                         /*
    1635                 :             :                          * We need not touch data->width, since a NOT operation does not
    1636                 :             :                          * change the match width.
    1637                 :             :                          */
    1638         [ -  + ]:       20198 :                         if (flags & TS_EXEC_SKIP_NOT)
    1639                 :             :                         {
    1640                 :             :                                 /* with SKIP_NOT, report NOT as "match everywhere" */
    1641         [ #  # ]:           0 :                                 Assert(data->npos == 0 && !data->negate);
    1642                 :           0 :                                 data->negate = true;
    1643                 :           0 :                                 return TS_YES;
    1644                 :             :                         }
    1645   [ -  +  +  + ]:       20198 :                         switch (TS_phrase_execute(curitem + 1, arg, flags, chkcond, data))
    1646                 :             :                         {
    1647                 :             :                                 case TS_NO:
    1648                 :             :                                         /* change "match nowhere" to "match everywhere" */
    1649         [ +  - ]:       17664 :                                         Assert(data->npos == 0 && !data->negate);
    1650                 :       17664 :                                         data->negate = true;
    1651                 :       17664 :                                         return TS_YES;
    1652                 :             :                                 case TS_YES:
    1653         [ +  + ]:          65 :                                         if (data->npos > 0)
    1654                 :             :                                         {
    1655                 :             :                                                 /* we have some positions, invert negate flag */
    1656                 :          64 :                                                 data->negate = !data->negate;
    1657                 :          64 :                                                 return TS_YES;
    1658                 :             :                                         }
    1659         [ +  - ]:           1 :                                         else if (data->negate)
    1660                 :             :                                         {
    1661                 :             :                                                 /* change "match everywhere" to "match nowhere" */
    1662                 :           1 :                                                 data->negate = false;
    1663                 :           1 :                                                 return TS_NO;
    1664                 :             :                                         }
    1665                 :             :                                         /* Should not get here if result was TS_YES */
    1666                 :           0 :                                         Assert(false);
    1667                 :           0 :                                         break;
    1668                 :             :                                 case TS_MAYBE:
    1669                 :             :                                         /* match positions are, and remain, uncertain */
    1670                 :        2469 :                                         return TS_MAYBE;
    1671                 :             :                         }
    1672                 :           0 :                         break;
    1673                 :             : 
    1674                 :             :                 case OP_PHRASE:
    1675                 :             :                 case OP_AND:
    1676                 :       39440 :                         memset(&Ldata, 0, sizeof(Ldata));
    1677                 :       39440 :                         memset(&Rdata, 0, sizeof(Rdata));
    1678                 :             : 
    1679                 :       78880 :                         lmatch = TS_phrase_execute(curitem + curitem->qoperator.left,
    1680                 :       39440 :                                                                            arg, flags, chkcond, &Ldata);
    1681         [ +  + ]:       39440 :                         if (lmatch == TS_NO)
    1682                 :       21095 :                                 return TS_NO;
    1683                 :             : 
    1684                 :       36690 :                         rmatch = TS_phrase_execute(curitem + 1,
    1685                 :       18345 :                                                                            arg, flags, chkcond, &Rdata);
    1686         [ +  + ]:       18345 :                         if (rmatch == TS_NO)
    1687                 :        9084 :                                 return TS_NO;
    1688                 :             : 
    1689                 :             :                         /*
    1690                 :             :                          * If either operand has no position information, then we can't
    1691                 :             :                          * return reliable position data, only a MAYBE result.
    1692                 :             :                          */
    1693   [ +  +  +  + ]:        9261 :                         if (lmatch == TS_MAYBE || rmatch == TS_MAYBE)
    1694                 :        4293 :                                 return TS_MAYBE;
    1695                 :             : 
    1696         [ +  + ]:        4968 :                         if (curitem->qoperator.oper == OP_PHRASE)
    1697                 :             :                         {
    1698                 :             :                                 /*
    1699                 :             :                                  * Compute Loffset and Roffset suitable for phrase match, and
    1700                 :             :                                  * compute overall width of whole phrase match.
    1701                 :             :                                  */
    1702                 :        4967 :                                 Loffset = curitem->qoperator.distance + Rdata.width;
    1703                 :        4967 :                                 Roffset = 0;
    1704         [ +  + ]:        4967 :                                 if (data)
    1705                 :          93 :                                         data->width = curitem->qoperator.distance +
    1706                 :          62 :                                                 Ldata.width + Rdata.width;
    1707                 :        4967 :                         }
    1708                 :             :                         else
    1709                 :             :                         {
    1710                 :             :                                 /*
    1711                 :             :                                  * For OP_AND, set output width and alignment like OP_OR (see
    1712                 :             :                                  * comment below)
    1713                 :             :                                  */
    1714         [ -  + ]:           1 :                                 maxwidth = Max(Ldata.width, Rdata.width);
    1715                 :           1 :                                 Loffset = maxwidth - Ldata.width;
    1716                 :           1 :                                 Roffset = maxwidth - Rdata.width;
    1717         [ -  + ]:           1 :                                 if (data)
    1718                 :           1 :                                         data->width = maxwidth;
    1719                 :             :                         }
    1720                 :             : 
    1721   [ +  +  +  + ]:        4968 :                         if (Ldata.negate && Rdata.negate)
    1722                 :             :                         {
    1723                 :             :                                 /* !L & !R: treat as !(L | R) */
    1724                 :        9478 :                                 (void) TS_phrase_output(data, &Ldata, &Rdata,
    1725                 :             :                                                                                 TSPO_BOTH | TSPO_L_ONLY | TSPO_R_ONLY,
    1726                 :        4739 :                                                                                 Loffset, Roffset,
    1727                 :        4739 :                                                                                 Ldata.npos + Rdata.npos);
    1728         [ +  - ]:        4739 :                                 if (data)
    1729                 :           0 :                                         data->negate = true;
    1730                 :        4739 :                                 return TS_YES;
    1731                 :             :                         }
    1732         [ +  + ]:         229 :                         else if (Ldata.negate)
    1733                 :             :                         {
    1734                 :             :                                 /* !L & R */
    1735                 :         150 :                                 return TS_phrase_output(data, &Ldata, &Rdata,
    1736                 :             :                                                                                 TSPO_R_ONLY,
    1737                 :          75 :                                                                                 Loffset, Roffset,
    1738                 :          75 :                                                                                 Rdata.npos);
    1739                 :             :                         }
    1740         [ +  + ]:         154 :                         else if (Rdata.negate)
    1741                 :             :                         {
    1742                 :             :                                 /* L & !R */
    1743                 :           2 :                                 return TS_phrase_output(data, &Ldata, &Rdata,
    1744                 :             :                                                                                 TSPO_L_ONLY,
    1745                 :           1 :                                                                                 Loffset, Roffset,
    1746                 :           1 :                                                                                 Ldata.npos);
    1747                 :             :                         }
    1748                 :             :                         else
    1749                 :             :                         {
    1750                 :             :                                 /* straight AND */
    1751                 :         306 :                                 return TS_phrase_output(data, &Ldata, &Rdata,
    1752                 :             :                                                                                 TSPO_BOTH,
    1753                 :         153 :                                                                                 Loffset, Roffset,
    1754         [ +  + ]:         153 :                                                                                 Min(Ldata.npos, Rdata.npos));
    1755                 :             :                         }
    1756                 :             : 
    1757                 :             :                 case OP_OR:
    1758                 :          26 :                         memset(&Ldata, 0, sizeof(Ldata));
    1759                 :          26 :                         memset(&Rdata, 0, sizeof(Rdata));
    1760                 :             : 
    1761                 :          52 :                         lmatch = TS_phrase_execute(curitem + curitem->qoperator.left,
    1762                 :          26 :                                                                            arg, flags, chkcond, &Ldata);
    1763                 :          52 :                         rmatch = TS_phrase_execute(curitem + 1,
    1764                 :          26 :                                                                            arg, flags, chkcond, &Rdata);
    1765                 :             : 
    1766   [ +  +  +  + ]:          26 :                         if (lmatch == TS_NO && rmatch == TS_NO)
    1767                 :           2 :                                 return TS_NO;
    1768                 :             : 
    1769                 :             :                         /*
    1770                 :             :                          * If either operand has no position information, then we can't
    1771                 :             :                          * return reliable position data, only a MAYBE result.
    1772                 :             :                          */
    1773   [ +  -  -  + ]:          24 :                         if (lmatch == TS_MAYBE || rmatch == TS_MAYBE)
    1774                 :           0 :                                 return TS_MAYBE;
    1775                 :             : 
    1776                 :             :                         /*
    1777                 :             :                          * Cope with undefined output width from failed submatch.  (This
    1778                 :             :                          * takes less code than trying to ensure that all failure returns
    1779                 :             :                          * set data->width to zero.)
    1780                 :             :                          */
    1781         [ +  + ]:          24 :                         if (lmatch == TS_NO)
    1782                 :           3 :                                 Ldata.width = 0;
    1783         [ +  + ]:          24 :                         if (rmatch == TS_NO)
    1784                 :          14 :                                 Rdata.width = 0;
    1785                 :             : 
    1786                 :             :                         /*
    1787                 :             :                          * For OP_AND and OP_OR, report the width of the wider of the two
    1788                 :             :                          * inputs, and align the narrower input's positions to the right
    1789                 :             :                          * end of that width.  This rule deals at least somewhat
    1790                 :             :                          * reasonably with cases like "x <-> (y | z <-> q)".
    1791                 :             :                          */
    1792         [ -  + ]:          24 :                         maxwidth = Max(Ldata.width, Rdata.width);
    1793                 :          24 :                         Loffset = maxwidth - Ldata.width;
    1794                 :          24 :                         Roffset = maxwidth - Rdata.width;
    1795                 :          24 :                         data->width = maxwidth;
    1796                 :             : 
    1797   [ +  +  +  + ]:          24 :                         if (Ldata.negate && Rdata.negate)
    1798                 :             :                         {
    1799                 :             :                                 /* !L | !R: treat as !(L & R) */
    1800                 :           2 :                                 (void) TS_phrase_output(data, &Ldata, &Rdata,
    1801                 :             :                                                                                 TSPO_BOTH,
    1802                 :           1 :                                                                                 Loffset, Roffset,
    1803         [ -  + ]:           1 :                                                                                 Min(Ldata.npos, Rdata.npos));
    1804                 :           1 :                                 data->negate = true;
    1805                 :           1 :                                 return TS_YES;
    1806                 :             :                         }
    1807         [ +  + ]:          23 :                         else if (Ldata.negate)
    1808                 :             :                         {
    1809                 :             :                                 /* !L | R: treat as !(L & !R) */
    1810                 :          10 :                                 (void) TS_phrase_output(data, &Ldata, &Rdata,
    1811                 :             :                                                                                 TSPO_L_ONLY,
    1812                 :           5 :                                                                                 Loffset, Roffset,
    1813                 :           5 :                                                                                 Ldata.npos);
    1814                 :           5 :                                 data->negate = true;
    1815                 :           5 :                                 return TS_YES;
    1816                 :             :                         }
    1817         [ +  + ]:          18 :                         else if (Rdata.negate)
    1818                 :             :                         {
    1819                 :             :                                 /* L | !R: treat as !(!L & R) */
    1820                 :           2 :                                 (void) TS_phrase_output(data, &Ldata, &Rdata,
    1821                 :             :                                                                                 TSPO_R_ONLY,
    1822                 :           1 :                                                                                 Loffset, Roffset,
    1823                 :           1 :                                                                                 Rdata.npos);
    1824                 :           1 :                                 data->negate = true;
    1825                 :           1 :                                 return TS_YES;
    1826                 :             :                         }
    1827                 :             :                         else
    1828                 :             :                         {
    1829                 :             :                                 /* straight OR */
    1830                 :          34 :                                 return TS_phrase_output(data, &Ldata, &Rdata,
    1831                 :             :                                                                                 TSPO_BOTH | TSPO_L_ONLY | TSPO_R_ONLY,
    1832                 :          17 :                                                                                 Loffset, Roffset,
    1833                 :          17 :                                                                                 Ldata.npos + Rdata.npos);
    1834                 :             :                         }
    1835                 :             : 
    1836                 :             :                 default:
    1837   [ #  #  #  # ]:           0 :                         elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper);
    1838                 :           0 :         }
    1839                 :             : 
    1840                 :             :         /* not reachable, but keep compiler quiet */
    1841                 :           0 :         return TS_NO;
    1842                 :      117433 : }
    1843                 :             : 
    1844                 :             : 
    1845                 :             : /*
    1846                 :             :  * Evaluate tsquery boolean expression.
    1847                 :             :  *
    1848                 :             :  * curitem: current tsquery item (initially, the first one)
    1849                 :             :  * arg: opaque value to pass through to callback function
    1850                 :             :  * flags: bitmask of flag bits shown in ts_utils.h
    1851                 :             :  * chkcond: callback function to check whether a primitive value is present
    1852                 :             :  */
    1853                 :             : bool
    1854                 :       86882 : TS_execute(QueryItem *curitem, void *arg, uint32 flags,
    1855                 :             :                    TSExecuteCallback chkcond)
    1856                 :             : {
    1857                 :             :         /*
    1858                 :             :          * If we get TS_MAYBE from the recursion, return true.  We could only see
    1859                 :             :          * that result if the caller passed TS_EXEC_PHRASE_NO_POS, so there's no
    1860                 :             :          * need to check again.
    1861                 :             :          */
    1862                 :       86882 :         return TS_execute_recurse(curitem, arg, flags, chkcond) != TS_NO;
    1863                 :             : }
    1864                 :             : 
    1865                 :             : /*
    1866                 :             :  * Evaluate tsquery boolean expression.
    1867                 :             :  *
    1868                 :             :  * This is the same as TS_execute except that TS_MAYBE is returned as-is.
    1869                 :             :  */
    1870                 :             : TSTernaryValue
    1871                 :        6157 : TS_execute_ternary(QueryItem *curitem, void *arg, uint32 flags,
    1872                 :             :                                    TSExecuteCallback chkcond)
    1873                 :             : {
    1874                 :        6157 :         return TS_execute_recurse(curitem, arg, flags, chkcond);
    1875                 :             : }
    1876                 :             : 
    1877                 :             : /*
    1878                 :             :  * TS_execute recursion for operators above any phrase operator.  Here we do
    1879                 :             :  * not need to worry about lexeme positions.  As soon as we hit an OP_PHRASE
    1880                 :             :  * operator, we pass it off to TS_phrase_execute which does worry.
    1881                 :             :  */
    1882                 :             : static TSTernaryValue
    1883                 :      175971 : TS_execute_recurse(QueryItem *curitem, void *arg, uint32 flags,
    1884                 :             :                                    TSExecuteCallback chkcond)
    1885                 :             : {
    1886                 :      175971 :         TSTernaryValue lmatch;
    1887                 :             : 
    1888                 :             :         /* since this function recurses, it could be driven to stack overflow */
    1889                 :      175971 :         check_stack_depth();
    1890                 :             : 
    1891                 :             :         /* ... and let's check for query cancel while we're at it */
    1892         [ +  - ]:      175971 :         CHECK_FOR_INTERRUPTS();
    1893                 :             : 
    1894         [ +  + ]:      175971 :         if (curitem->type == QI_VAL)
    1895                 :       70626 :                 return chkcond(arg, (QueryOperand *) curitem,
    1896                 :             :                                            NULL /* don't need position info */ );
    1897                 :             : 
    1898   [ +  +  +  +  :      105345 :         switch (curitem->qoperator.oper)
                      - ]
    1899                 :             :         {
    1900                 :             :                 case OP_NOT:
    1901         [ -  + ]:       33864 :                         if (flags & TS_EXEC_SKIP_NOT)
    1902                 :           0 :                                 return TS_YES;
    1903   [ +  -  +  + ]:       33864 :                         switch (TS_execute_recurse(curitem + 1, arg, flags, chkcond))
    1904                 :             :                         {
    1905                 :             :                                 case TS_NO:
    1906                 :       31951 :                                         return TS_YES;
    1907                 :             :                                 case TS_YES:
    1908                 :         815 :                                         return TS_NO;
    1909                 :             :                                 case TS_MAYBE:
    1910                 :        1098 :                                         return TS_MAYBE;
    1911                 :             :                         }
    1912                 :           0 :                         break;
    1913                 :             : 
    1914                 :             :                 case OP_AND:
    1915                 :       27888 :                         lmatch = TS_execute_recurse(curitem + curitem->qoperator.left, arg,
    1916                 :       13944 :                                                                                 flags, chkcond);
    1917         [ +  + ]:       13944 :                         if (lmatch == TS_NO)
    1918                 :       11088 :                                 return TS_NO;
    1919   [ +  -  +  + ]:        2856 :                         switch (TS_execute_recurse(curitem + 1, arg, flags, chkcond))
    1920                 :             :                         {
    1921                 :             :                                 case TS_NO:
    1922                 :        1684 :                                         return TS_NO;
    1923                 :             :                                 case TS_YES:
    1924                 :         550 :                                         return lmatch;
    1925                 :             :                                 case TS_MAYBE:
    1926                 :         622 :                                         return TS_MAYBE;
    1927                 :             :                         }
    1928                 :           0 :                         break;
    1929                 :             : 
    1930                 :             :                 case OP_OR:
    1931                 :       36298 :                         lmatch = TS_execute_recurse(curitem + curitem->qoperator.left, arg,
    1932                 :       18149 :                                                                                 flags, chkcond);
    1933         [ +  + ]:       18149 :                         if (lmatch == TS_YES)
    1934                 :        4030 :                                 return TS_YES;
    1935   [ +  -  +  + ]:       14119 :                         switch (TS_execute_recurse(curitem + 1, arg, flags, chkcond))
    1936                 :             :                         {
    1937                 :             :                                 case TS_NO:
    1938                 :        9584 :                                         return lmatch;
    1939                 :             :                                 case TS_YES:
    1940                 :        1236 :                                         return TS_YES;
    1941                 :             :                                 case TS_MAYBE:
    1942                 :        3299 :                                         return TS_MAYBE;
    1943                 :             :                         }
    1944                 :           0 :                         break;
    1945                 :             : 
    1946                 :             :                 case OP_PHRASE:
    1947                 :             : 
    1948                 :             :                         /*
    1949                 :             :                          * If we get a MAYBE result, and the caller doesn't want that,
    1950                 :             :                          * convert it to NO.  It would be more consistent, perhaps, to
    1951                 :             :                          * return the result of TS_phrase_execute() verbatim and then
    1952                 :             :                          * convert MAYBE results at the top of the recursion.  But
    1953                 :             :                          * converting at the topmost phrase operator gives results that
    1954                 :             :                          * are bug-compatible with the old implementation, so do it like
    1955                 :             :                          * this for now.
    1956                 :             :                          */
    1957   [ +  -  +  + ]:       39388 :                         switch (TS_phrase_execute(curitem, arg, flags, chkcond, NULL))
    1958                 :             :                         {
    1959                 :             :                                 case TS_NO:
    1960                 :       30211 :                                         return TS_NO;
    1961                 :             :                                 case TS_YES:
    1962                 :        4885 :                                         return TS_YES;
    1963                 :             :                                 case TS_MAYBE:
    1964                 :        4292 :                                         return (flags & TS_EXEC_PHRASE_NO_POS) ? TS_MAYBE : TS_NO;
    1965                 :             :                         }
    1966                 :           0 :                         break;
    1967                 :             : 
    1968                 :             :                 default:
    1969   [ #  #  #  # ]:           0 :                         elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper);
    1970                 :           0 :         }
    1971                 :             : 
    1972                 :             :         /* not reachable, but keep compiler quiet */
    1973                 :           0 :         return TS_NO;
    1974                 :      175971 : }
    1975                 :             : 
    1976                 :             : /*
    1977                 :             :  * Evaluate tsquery and report locations of matching terms.
    1978                 :             :  *
    1979                 :             :  * This is like TS_execute except that it returns match locations not just
    1980                 :             :  * success/failure status.  The callback function is required to provide
    1981                 :             :  * position data (we report failure if it doesn't).
    1982                 :             :  *
    1983                 :             :  * On successful match, the result is a List of ExecPhraseData structs, one
    1984                 :             :  * for each AND'ed term or phrase operator in the query.  Each struct includes
    1985                 :             :  * a sorted array of lexeme positions matching that term.  (Recall that for
    1986                 :             :  * phrase operators, the match includes width+1 lexemes, and the recorded
    1987                 :             :  * position is that of the rightmost lexeme.)
    1988                 :             :  *
    1989                 :             :  * OR subexpressions are handled by union'ing their match locations into a
    1990                 :             :  * single List element, which is valid since any of those locations contains
    1991                 :             :  * a match.  However, when some of the OR'ed terms are phrase operators, we
    1992                 :             :  * report the maximum width of any of the OR'ed terms, making such cases
    1993                 :             :  * slightly imprecise in the conservative direction.  (For example, if the
    1994                 :             :  * tsquery is "(A <-> B) | C", an occurrence of C in the data would be
    1995                 :             :  * reported as though it includes the lexeme to the left of C.)
    1996                 :             :  *
    1997                 :             :  * Locations of NOT subexpressions are not reported.  (Obviously, there can
    1998                 :             :  * be no successful NOT matches at top level, or the match would have failed.
    1999                 :             :  * So this amounts to ignoring NOTs underneath ORs.)
    2000                 :             :  *
    2001                 :             :  * The result is NIL if no match, or if position data was not returned.
    2002                 :             :  *
    2003                 :             :  * Arguments are the same as for TS_execute, although flags is currently
    2004                 :             :  * vestigial since none of the defined bits are sensible here.
    2005                 :             :  */
    2006                 :             : List *
    2007                 :          60 : TS_execute_locations(QueryItem *curitem, void *arg,
    2008                 :             :                                          uint32 flags,
    2009                 :             :                                          TSExecuteCallback chkcond)
    2010                 :             : {
    2011                 :          60 :         List       *result;
    2012                 :             : 
    2013                 :             :         /* No flags supported, as yet */
    2014         [ +  - ]:          60 :         Assert(flags == TS_EXEC_EMPTY);
    2015         [ +  + ]:          60 :         if (TS_execute_locations_recurse(curitem, arg, chkcond, &result))
    2016                 :          21 :                 return result;
    2017                 :          39 :         return NIL;
    2018                 :          60 : }
    2019                 :             : 
    2020                 :             : /*
    2021                 :             :  * TS_execute_locations recursion for operators above any phrase operator.
    2022                 :             :  * OP_PHRASE subexpressions can be passed off to TS_phrase_execute.
    2023                 :             :  */
    2024                 :             : static bool
    2025                 :         178 : TS_execute_locations_recurse(QueryItem *curitem, void *arg,
    2026                 :             :                                                          TSExecuteCallback chkcond,
    2027                 :             :                                                          List **locations)
    2028                 :             : {
    2029                 :         178 :         bool            lmatch,
    2030                 :             :                                 rmatch;
    2031                 :         178 :         List       *llocations,
    2032                 :             :                            *rlocations;
    2033                 :         178 :         ExecPhraseData *data;
    2034                 :             : 
    2035                 :             :         /* since this function recurses, it could be driven to stack overflow */
    2036                 :         178 :         check_stack_depth();
    2037                 :             : 
    2038                 :             :         /* ... and let's check for query cancel while we're at it */
    2039         [ +  - ]:         178 :         CHECK_FOR_INTERRUPTS();
    2040                 :             : 
    2041                 :             :         /* Default locations result is empty */
    2042                 :         178 :         *locations = NIL;
    2043                 :             : 
    2044         [ +  + ]:         178 :         if (curitem->type == QI_VAL)
    2045                 :             :         {
    2046                 :          74 :                 data = palloc0_object(ExecPhraseData);
    2047         [ +  + ]:          74 :                 if (chkcond(arg, (QueryOperand *) curitem, data) == TS_YES)
    2048                 :             :                 {
    2049                 :          35 :                         *locations = list_make1(data);
    2050                 :          35 :                         return true;
    2051                 :             :                 }
    2052                 :          39 :                 pfree(data);
    2053                 :          39 :                 return false;
    2054                 :             :         }
    2055                 :             : 
    2056   [ +  +  +  +  :         104 :         switch (curitem->qoperator.oper)
                      - ]
    2057                 :             :         {
    2058                 :             :                 case OP_NOT:
    2059         [ +  - ]:           2 :                         if (!TS_execute_locations_recurse(curitem + 1, arg, chkcond,
    2060                 :             :                                                                                           &llocations))
    2061                 :           0 :                                 return true;    /* we don't pass back any locations */
    2062                 :           2 :                         return false;
    2063                 :             : 
    2064                 :             :                 case OP_AND:
    2065   [ +  +  +  + ]:         176 :                         if (!TS_execute_locations_recurse(curitem + curitem->qoperator.left,
    2066                 :          88 :                                                                                           arg, chkcond,
    2067                 :             :                                                                                           &llocations))
    2068                 :          68 :                                 return false;
    2069   [ +  +  +  + ]:          40 :                         if (!TS_execute_locations_recurse(curitem + 1,
    2070                 :          20 :                                                                                           arg, chkcond,
    2071                 :             :                                                                                           &rlocations))
    2072                 :           9 :                                 return false;
    2073                 :          11 :                         *locations = list_concat(llocations, rlocations);
    2074                 :          11 :                         return true;
    2075                 :             : 
    2076                 :             :                 case OP_OR:
    2077                 :           8 :                         lmatch = TS_execute_locations_recurse(curitem + curitem->qoperator.left,
    2078                 :           4 :                                                                                                   arg, chkcond,
    2079                 :             :                                                                                                   &llocations);
    2080                 :           8 :                         rmatch = TS_execute_locations_recurse(curitem + 1,
    2081                 :           4 :                                                                                                   arg, chkcond,
    2082                 :             :                                                                                                   &rlocations);
    2083   [ -  +  #  # ]:           4 :                         if (lmatch || rmatch)
    2084                 :             :                         {
    2085                 :             :                                 /*
    2086                 :             :                                  * We generate an AND'able location struct from each
    2087                 :             :                                  * combination of sub-matches, following the disjunctive law
    2088                 :             :                                  * (A & B) | (C & D) = (A | C) & (A | D) & (B | C) & (B | D).
    2089                 :             :                                  *
    2090                 :             :                                  * However, if either input didn't produce locations (i.e., it
    2091                 :             :                                  * failed or was a NOT), we must just return the other list.
    2092                 :             :                                  */
    2093         [ +  - ]:           4 :                                 if (llocations == NIL)
    2094                 :           0 :                                         *locations = rlocations;
    2095         [ +  + ]:           4 :                                 else if (rlocations == NIL)
    2096                 :           2 :                                         *locations = llocations;
    2097                 :             :                                 else
    2098                 :             :                                 {
    2099                 :           2 :                                         ListCell   *ll;
    2100                 :             : 
    2101   [ +  -  +  +  :           4 :                                         foreach(ll, llocations)
                   +  + ]
    2102                 :             :                                         {
    2103                 :           2 :                                                 ExecPhraseData *ldata = (ExecPhraseData *) lfirst(ll);
    2104                 :           2 :                                                 ListCell   *lr;
    2105                 :             : 
    2106   [ +  -  +  +  :           4 :                                                 foreach(lr, rlocations)
                   +  + ]
    2107                 :             :                                                 {
    2108                 :           2 :                                                         ExecPhraseData *rdata = (ExecPhraseData *) lfirst(lr);
    2109                 :             : 
    2110                 :           2 :                                                         data = palloc0_object(ExecPhraseData);
    2111                 :           4 :                                                         (void) TS_phrase_output(data, ldata, rdata,
    2112                 :             :                                                                                                         TSPO_BOTH | TSPO_L_ONLY | TSPO_R_ONLY,
    2113                 :             :                                                                                                         0, 0,
    2114                 :           2 :                                                                                                         ldata->npos + rdata->npos);
    2115                 :             :                                                         /* Report the larger width, as explained above. */
    2116         [ +  + ]:           2 :                                                         data->width = Max(ldata->width, rdata->width);
    2117                 :           2 :                                                         *locations = lappend(*locations, data);
    2118                 :           2 :                                                 }
    2119                 :           2 :                                         }
    2120                 :           2 :                                 }
    2121                 :             : 
    2122                 :           4 :                                 return true;
    2123                 :             :                         }
    2124                 :           0 :                         return false;
    2125                 :             : 
    2126                 :             :                 case OP_PHRASE:
    2127                 :             :                         /* We can hand this off to TS_phrase_execute */
    2128                 :          10 :                         data = palloc0_object(ExecPhraseData);
    2129                 :          20 :                         if (TS_phrase_execute(curitem, arg, TS_EXEC_EMPTY, chkcond,
    2130   [ +  -  +  - ]:          20 :                                                                   data) == TS_YES)
    2131                 :             :                         {
    2132         [ -  + ]:          10 :                                 if (!data->negate)
    2133                 :          10 :                                         *locations = list_make1(data);
    2134                 :          10 :                                 return true;
    2135                 :             :                         }
    2136                 :           0 :                         pfree(data);
    2137                 :           0 :                         return false;
    2138                 :             : 
    2139                 :             :                 default:
    2140   [ #  #  #  # ]:           0 :                         elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper);
    2141                 :           0 :         }
    2142                 :             : 
    2143                 :             :         /* not reachable, but keep compiler quiet */
    2144                 :           0 :         return false;
    2145                 :         178 : }
    2146                 :             : 
    2147                 :             : /*
    2148                 :             :  * Detect whether a tsquery boolean expression requires any positive matches
    2149                 :             :  * to values shown in the tsquery.
    2150                 :             :  *
    2151                 :             :  * This is needed to know whether a GIN index search requires full index scan.
    2152                 :             :  * For example, 'x & !y' requires a match of x, so it's sufficient to scan
    2153                 :             :  * entries for x; but 'x | !y' could match rows containing neither x nor y.
    2154                 :             :  */
    2155                 :             : bool
    2156                 :         139 : tsquery_requires_match(QueryItem *curitem)
    2157                 :             : {
    2158                 :             :         /* since this function recurses, it could be driven to stack overflow */
    2159                 :         139 :         check_stack_depth();
    2160                 :             : 
    2161         [ +  + ]:         139 :         if (curitem->type == QI_VAL)
    2162                 :          66 :                 return true;
    2163                 :             : 
    2164   [ +  +  +  - ]:          73 :         switch (curitem->qoperator.oper)
    2165                 :             :         {
    2166                 :             :                 case OP_NOT:
    2167                 :             : 
    2168                 :             :                         /*
    2169                 :             :                          * Assume there are no required matches underneath a NOT.  For
    2170                 :             :                          * some cases with nested NOTs, we could prove there's a required
    2171                 :             :                          * match, but it seems unlikely to be worth the trouble.
    2172                 :             :                          */
    2173                 :          28 :                         return false;
    2174                 :             : 
    2175                 :             :                 case OP_PHRASE:
    2176                 :             : 
    2177                 :             :                         /*
    2178                 :             :                          * Treat OP_PHRASE as OP_AND here
    2179                 :             :                          */
    2180                 :             :                 case OP_AND:
    2181                 :             :                         /* If either side requires a match, we're good */
    2182         [ +  + ]:          34 :                         if (tsquery_requires_match(curitem + curitem->qoperator.left))
    2183                 :          26 :                                 return true;
    2184                 :             :                         else
    2185                 :           8 :                                 return tsquery_requires_match(curitem + 1);
    2186                 :             : 
    2187                 :             :                 case OP_OR:
    2188                 :             :                         /* Both sides must require a match */
    2189         [ +  - ]:          11 :                         if (tsquery_requires_match(curitem + curitem->qoperator.left))
    2190                 :          11 :                                 return tsquery_requires_match(curitem + 1);
    2191                 :             :                         else
    2192                 :           0 :                                 return false;
    2193                 :             : 
    2194                 :             :                 default:
    2195   [ #  #  #  # ]:           0 :                         elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper);
    2196                 :           0 :         }
    2197                 :             : 
    2198                 :             :         /* not reachable, but keep compiler quiet */
    2199                 :           0 :         return false;
    2200                 :         139 : }
    2201                 :             : 
    2202                 :             : /*
    2203                 :             :  * boolean operations
    2204                 :             :  */
    2205                 :             : Datum
    2206                 :          10 : ts_match_qv(PG_FUNCTION_ARGS)
    2207                 :             : {
    2208                 :          10 :         PG_RETURN_DATUM(DirectFunctionCall2(ts_match_vq,
    2209                 :             :                                                                                 PG_GETARG_DATUM(1),
    2210                 :             :                                                                                 PG_GETARG_DATUM(0)));
    2211                 :             : }
    2212                 :             : 
    2213                 :             : Datum
    2214                 :       36680 : ts_match_vq(PG_FUNCTION_ARGS)
    2215                 :             : {
    2216                 :       36680 :         TSVector        val = PG_GETARG_TSVECTOR(0);
    2217                 :       36680 :         TSQuery         query = PG_GETARG_TSQUERY(1);
    2218                 :       36680 :         CHKVAL          chkval;
    2219                 :       36680 :         bool            result;
    2220                 :             : 
    2221                 :             :         /* empty query matches nothing */
    2222         [ +  - ]:       36680 :         if (!query->size)
    2223                 :             :         {
    2224         [ #  # ]:           0 :                 PG_FREE_IF_COPY(val, 0);
    2225         [ #  # ]:           0 :                 PG_FREE_IF_COPY(query, 1);
    2226                 :           0 :                 PG_RETURN_BOOL(false);
    2227                 :             :         }
    2228                 :             : 
    2229                 :       36680 :         chkval.arrb = ARRPTR(val);
    2230                 :       36680 :         chkval.arre = chkval.arrb + val->size;
    2231                 :       36680 :         chkval.values = STRPTR(val);
    2232                 :       36680 :         chkval.operand = GETOPERAND(query);
    2233                 :       36680 :         result = TS_execute(GETQUERY(query),
    2234                 :             :                                                 &chkval,
    2235                 :             :                                                 TS_EXEC_EMPTY,
    2236                 :             :                                                 checkcondition_str);
    2237                 :             : 
    2238         [ +  + ]:       36680 :         PG_FREE_IF_COPY(val, 0);
    2239         [ +  - ]:       36680 :         PG_FREE_IF_COPY(query, 1);
    2240                 :       36680 :         PG_RETURN_BOOL(result);
    2241                 :       36680 : }
    2242                 :             : 
    2243                 :             : Datum
    2244                 :           0 : ts_match_tt(PG_FUNCTION_ARGS)
    2245                 :             : {
    2246                 :           0 :         TSVector        vector;
    2247                 :           0 :         TSQuery         query;
    2248                 :           0 :         bool            res;
    2249                 :             : 
    2250                 :           0 :         vector = DatumGetTSVector(DirectFunctionCall1(to_tsvector,
    2251                 :             :                                                                                                   PG_GETARG_DATUM(0)));
    2252                 :           0 :         query = DatumGetTSQuery(DirectFunctionCall1(plainto_tsquery,
    2253                 :             :                                                                                                 PG_GETARG_DATUM(1)));
    2254                 :             : 
    2255                 :           0 :         res = DatumGetBool(DirectFunctionCall2(ts_match_vq,
    2256                 :             :                                                                                    TSVectorGetDatum(vector),
    2257                 :             :                                                                                    TSQueryGetDatum(query)));
    2258                 :             : 
    2259                 :           0 :         pfree(vector);
    2260                 :           0 :         pfree(query);
    2261                 :             : 
    2262                 :           0 :         PG_RETURN_BOOL(res);
    2263                 :           0 : }
    2264                 :             : 
    2265                 :             : Datum
    2266                 :           0 : ts_match_tq(PG_FUNCTION_ARGS)
    2267                 :             : {
    2268                 :           0 :         TSVector        vector;
    2269                 :           0 :         TSQuery         query = PG_GETARG_TSQUERY(1);
    2270                 :           0 :         bool            res;
    2271                 :             : 
    2272                 :           0 :         vector = DatumGetTSVector(DirectFunctionCall1(to_tsvector,
    2273                 :             :                                                                                                   PG_GETARG_DATUM(0)));
    2274                 :             : 
    2275                 :           0 :         res = DatumGetBool(DirectFunctionCall2(ts_match_vq,
    2276                 :             :                                                                                    TSVectorGetDatum(vector),
    2277                 :             :                                                                                    TSQueryGetDatum(query)));
    2278                 :             : 
    2279                 :           0 :         pfree(vector);
    2280         [ #  # ]:           0 :         PG_FREE_IF_COPY(query, 1);
    2281                 :             : 
    2282                 :           0 :         PG_RETURN_BOOL(res);
    2283                 :           0 : }
    2284                 :             : 
    2285                 :             : /*
    2286                 :             :  * ts_stat statistic function support
    2287                 :             :  */
    2288                 :             : 
    2289                 :             : 
    2290                 :             : /*
    2291                 :             :  * Returns the number of positions in value 'wptr' within tsvector 'txt',
    2292                 :             :  * that have a weight equal to one of the weights in 'weight' bitmask.
    2293                 :             :  */
    2294                 :             : static int
    2295                 :        1363 : check_weight(TSVector txt, WordEntry *wptr, int8 weight)
    2296                 :             : {
    2297         [ +  - ]:        1363 :         int                     len = POSDATALEN(txt, wptr);
    2298                 :        1363 :         int                     num = 0;
    2299                 :        1363 :         WordEntryPos *ptr = POSDATAPTR(txt, wptr);
    2300                 :             : 
    2301         [ +  + ]:        2775 :         while (len--)
    2302                 :             :         {
    2303         [ +  + ]:        1412 :                 if (weight & (1 << WEP_GETWEIGHT(*ptr)))
    2304                 :           2 :                         num++;
    2305                 :        1412 :                 ptr++;
    2306                 :             :         }
    2307                 :        2726 :         return num;
    2308                 :        1363 : }
    2309                 :             : 
    2310                 :             : #define compareStatWord(a,e,t)                                                  \
    2311                 :             :         tsCompareString((a)->lexeme, (a)->lenlexeme,              \
    2312                 :             :                                         STRPTR(t) + (e)->pos, (e)->len,           \
    2313                 :             :                                         false)
    2314                 :             : 
    2315                 :             : static void
    2316                 :       57604 : insertStatEntry(MemoryContext persistentContext, TSVectorStat *stat, TSVector txt, uint32 off)
    2317                 :             : {
    2318                 :       57604 :         WordEntry  *we = ARRPTR(txt) + off;
    2319                 :       57604 :         StatEntry  *node = stat->root,
    2320                 :       57604 :                            *pnode = NULL;
    2321                 :       57604 :         int                     n,
    2322                 :       57604 :                                 res = 0;
    2323                 :       57604 :         uint32          depth = 1;
    2324                 :             : 
    2325         [ +  + ]:       57604 :         if (stat->weight == 0)
    2326   [ +  +  +  - ]:       28802 :                 n = (we->haspos) ? POSDATALEN(txt, we) : 1;
    2327                 :             :         else
    2328         [ +  + ]:       28802 :                 n = (we->haspos) ? check_weight(txt, we, stat->weight) : 0;
    2329                 :             : 
    2330         [ +  + ]:       57604 :         if (n == 0)
    2331                 :       28801 :                 return;                                 /* nothing to insert */
    2332                 :             : 
    2333         [ +  + ]:      290895 :         while (node)
    2334                 :             :         {
    2335                 :      289751 :                 res = compareStatWord(node, we, txt);
    2336                 :             : 
    2337         [ +  + ]:      289751 :                 if (res == 0)
    2338                 :             :                 {
    2339                 :       27659 :                         break;
    2340                 :             :                 }
    2341                 :             :                 else
    2342                 :             :                 {
    2343                 :      262092 :                         pnode = node;
    2344         [ +  + ]:      262092 :                         node = (res < 0) ? node->left : node->right;
    2345                 :             :                 }
    2346                 :      262092 :                 depth++;
    2347                 :             :         }
    2348                 :             : 
    2349         [ +  + ]:       28803 :         if (depth > stat->maxdepth)
    2350                 :          21 :                 stat->maxdepth = depth;
    2351                 :             : 
    2352         [ +  + ]:       28803 :         if (node == NULL)
    2353                 :             :         {
    2354                 :        1144 :                 node = MemoryContextAlloc(persistentContext, STATENTRYHDRSZ + we->len);
    2355                 :        1144 :                 node->left = node->right = NULL;
    2356                 :        1144 :                 node->ndoc = 1;
    2357                 :        1144 :                 node->nentry = n;
    2358                 :        1144 :                 node->lenlexeme = we->len;
    2359                 :        1144 :                 memcpy(node->lexeme, STRPTR(txt) + we->pos, node->lenlexeme);
    2360                 :             : 
    2361         [ +  + ]:        1144 :                 if (pnode == NULL)
    2362                 :             :                 {
    2363                 :           2 :                         stat->root = node;
    2364                 :           2 :                 }
    2365                 :             :                 else
    2366                 :             :                 {
    2367         [ +  + ]:        1142 :                         if (res < 0)
    2368                 :         564 :                                 pnode->left = node;
    2369                 :             :                         else
    2370                 :         578 :                                 pnode->right = node;
    2371                 :             :                 }
    2372                 :        1144 :         }
    2373                 :             :         else
    2374                 :             :         {
    2375                 :       27659 :                 node->ndoc++;
    2376                 :       27659 :                 node->nentry += n;
    2377                 :             :         }
    2378         [ -  + ]:       57604 : }
    2379                 :             : 
    2380                 :             : static void
    2381                 :       82564 : chooseNextStatEntry(MemoryContext persistentContext, TSVectorStat *stat, TSVector txt,
    2382                 :             :                                         uint32 low, uint32 high, uint32 offset)
    2383                 :             : {
    2384                 :       82564 :         uint32          pos;
    2385                 :       82564 :         uint32          middle = (low + high) >> 1;
    2386                 :             : 
    2387                 :       82564 :         pos = (low + middle) >> 1;
    2388   [ +  +  +  +  :       82564 :         if (low != middle && pos >= offset && pos - offset < txt->size)
                   +  + ]
    2389                 :       28388 :                 insertStatEntry(persistentContext, stat, txt, pos - offset);
    2390                 :       82564 :         pos = (high + middle + 1) >> 1;
    2391   [ +  +  +  +  :       82564 :         if (middle + 1 != high && pos >= offset && pos - offset < txt->size)
                   +  + ]
    2392                 :       28214 :                 insertStatEntry(persistentContext, stat, txt, pos - offset);
    2393                 :             : 
    2394         [ +  + ]:       82564 :         if (low != middle)
    2395                 :       41282 :                 chooseNextStatEntry(persistentContext, stat, txt, low, middle, offset);
    2396         [ +  + ]:       82564 :         if (high != middle + 1)
    2397                 :       40280 :                 chooseNextStatEntry(persistentContext, stat, txt, middle + 1, high, offset);
    2398                 :       82564 : }
    2399                 :             : 
    2400                 :             : /*
    2401                 :             :  * This is written like a custom aggregate function, because the
    2402                 :             :  * original plan was to do just that. Unfortunately, an aggregate function
    2403                 :             :  * can't return a set, so that plan was abandoned. If that limitation is
    2404                 :             :  * lifted in the future, ts_stat could be a real aggregate function so that
    2405                 :             :  * you could use it like this:
    2406                 :             :  *
    2407                 :             :  *       SELECT ts_stat(vector_column) FROM vector_table;
    2408                 :             :  *
    2409                 :             :  *      where vector_column is a tsvector-type column in vector_table.
    2410                 :             :  */
    2411                 :             : 
    2412                 :             : static TSVectorStat *
    2413                 :        1018 : ts_accum(MemoryContext persistentContext, TSVectorStat *stat, Datum data)
    2414                 :             : {
    2415                 :        1018 :         TSVector        txt = DatumGetTSVector(data);
    2416                 :        2036 :         uint32          i,
    2417                 :        1018 :                                 nbit = 0,
    2418                 :             :                                 offset;
    2419                 :             : 
    2420         [ +  - ]:        1018 :         if (stat == NULL)
    2421                 :             :         {                                                       /* Init in first */
    2422                 :           0 :                 stat = MemoryContextAllocZero(persistentContext, sizeof(TSVectorStat));
    2423                 :           0 :                 stat->maxdepth = 1;
    2424                 :           0 :         }
    2425                 :             : 
    2426                 :             :         /* simple check of correctness */
    2427   [ +  -  +  + ]:        1018 :         if (txt == NULL || txt->size == 0)
    2428                 :             :         {
    2429   [ +  -  +  - ]:          16 :                 if (txt && txt != (TSVector) DatumGetPointer(data))
    2430                 :          16 :                         pfree(txt);
    2431                 :          16 :                 return stat;
    2432                 :             :         }
    2433                 :             : 
    2434                 :        1002 :         i = txt->size - 1;
    2435         [ +  + ]:        7120 :         for (; i > 0; i >>= 1)
    2436                 :        6118 :                 nbit++;
    2437                 :             : 
    2438                 :        1002 :         nbit = 1 << nbit;
    2439                 :        1002 :         offset = (nbit - txt->size) / 2;
    2440                 :             : 
    2441                 :        1002 :         insertStatEntry(persistentContext, stat, txt, (nbit >> 1) - offset);
    2442                 :        1002 :         chooseNextStatEntry(persistentContext, stat, txt, 0, nbit, offset);
    2443                 :             : 
    2444                 :        1002 :         return stat;
    2445                 :        1018 : }
    2446                 :             : 
    2447                 :             : static void
    2448                 :           2 : ts_setup_firstcall(FunctionCallInfo fcinfo, FuncCallContext *funcctx,
    2449                 :             :                                    TSVectorStat *stat)
    2450                 :             : {
    2451                 :           2 :         TupleDesc       tupdesc;
    2452                 :           2 :         MemoryContext oldcontext;
    2453                 :           2 :         StatEntry  *node;
    2454                 :             : 
    2455                 :           2 :         funcctx->user_fctx = stat;
    2456                 :             : 
    2457                 :           2 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
    2458                 :             : 
    2459                 :           2 :         stat->stack = palloc0_array(StatEntry *, stat->maxdepth + 1);
    2460                 :           2 :         stat->stackpos = 0;
    2461                 :             : 
    2462                 :           2 :         node = stat->root;
    2463                 :             :         /* find leftmost value */
    2464         [ +  - ]:           2 :         if (node == NULL)
    2465                 :           0 :                 stat->stack[stat->stackpos] = NULL;
    2466                 :             :         else
    2467                 :           8 :                 for (;;)
    2468                 :             :                 {
    2469                 :           8 :                         stat->stack[stat->stackpos] = node;
    2470         [ +  + ]:           8 :                         if (node->left)
    2471                 :             :                         {
    2472                 :           6 :                                 stat->stackpos++;
    2473                 :           6 :                                 node = node->left;
    2474                 :           6 :                         }
    2475                 :             :                         else
    2476                 :           2 :                                 break;
    2477                 :             :                 }
    2478         [ +  - ]:           2 :         Assert(stat->stackpos <= stat->maxdepth);
    2479                 :             : 
    2480         [ +  - ]:           2 :         if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
    2481   [ #  #  #  # ]:           0 :                 elog(ERROR, "return type must be a row type");
    2482                 :           2 :         funcctx->tuple_desc = tupdesc;
    2483                 :           2 :         funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
    2484                 :             : 
    2485                 :           2 :         MemoryContextSwitchTo(oldcontext);
    2486                 :           2 : }
    2487                 :             : 
    2488                 :             : static StatEntry *
    2489                 :        2288 : walkStatEntryTree(TSVectorStat *stat)
    2490                 :             : {
    2491                 :        2288 :         StatEntry  *node = stat->stack[stat->stackpos];
    2492                 :             : 
    2493         [ +  - ]:        2288 :         if (node == NULL)
    2494                 :           0 :                 return NULL;
    2495                 :             : 
    2496         [ +  + ]:        2288 :         if (node->ndoc != 0)
    2497                 :             :         {
    2498                 :             :                 /* return entry itself: we already was at left sublink */
    2499                 :         566 :                 return node;
    2500                 :             :         }
    2501   [ +  +  +  + ]:        1722 :         else if (node->right && node->right != stat->stack[stat->stackpos + 1])
    2502                 :             :         {
    2503                 :             :                 /* go on right sublink */
    2504                 :         578 :                 stat->stackpos++;
    2505                 :         578 :                 node = node->right;
    2506                 :             : 
    2507                 :             :                 /* find most-left value */
    2508                 :        1136 :                 for (;;)
    2509                 :             :                 {
    2510                 :        1136 :                         stat->stack[stat->stackpos] = node;
    2511         [ +  + ]:        1136 :                         if (node->left)
    2512                 :             :                         {
    2513                 :         558 :                                 stat->stackpos++;
    2514                 :         558 :                                 node = node->left;
    2515                 :         558 :                         }
    2516                 :             :                         else
    2517                 :         578 :                                 break;
    2518                 :             :                 }
    2519         [ +  - ]:         578 :                 Assert(stat->stackpos <= stat->maxdepth);
    2520                 :         578 :         }
    2521                 :             :         else
    2522                 :             :         {
    2523                 :             :                 /* we already return all left subtree, itself and  right subtree */
    2524         [ +  + ]:        1144 :                 if (stat->stackpos == 0)
    2525                 :           2 :                         return NULL;
    2526                 :             : 
    2527                 :        1142 :                 stat->stackpos--;
    2528                 :        1142 :                 return walkStatEntryTree(stat);
    2529                 :             :         }
    2530                 :             : 
    2531                 :         578 :         return node;
    2532                 :        2288 : }
    2533                 :             : 
    2534                 :             : static Datum
    2535                 :        1146 : ts_process_call(FuncCallContext *funcctx)
    2536                 :             : {
    2537                 :        1146 :         TSVectorStat *st;
    2538                 :        1146 :         StatEntry  *entry;
    2539                 :             : 
    2540                 :        1146 :         st = (TSVectorStat *) funcctx->user_fctx;
    2541                 :             : 
    2542                 :        1146 :         entry = walkStatEntryTree(st);
    2543                 :             : 
    2544         [ +  + ]:        1146 :         if (entry != NULL)
    2545                 :             :         {
    2546                 :        1144 :                 Datum           result;
    2547                 :        1144 :                 char       *values[3];
    2548                 :        1144 :                 char            ndoc[16];
    2549                 :        1144 :                 char            nentry[16];
    2550                 :        1144 :                 HeapTuple       tuple;
    2551                 :             : 
    2552                 :        1144 :                 values[0] = palloc(entry->lenlexeme + 1);
    2553                 :        1144 :                 memcpy(values[0], entry->lexeme, entry->lenlexeme);
    2554                 :        1144 :                 (values[0])[entry->lenlexeme] = '\0';
    2555                 :        1144 :                 sprintf(ndoc, "%d", entry->ndoc);
    2556                 :        1144 :                 values[1] = ndoc;
    2557                 :        1144 :                 sprintf(nentry, "%d", entry->nentry);
    2558                 :        1144 :                 values[2] = nentry;
    2559                 :             : 
    2560                 :        1144 :                 tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
    2561                 :        1144 :                 result = HeapTupleGetDatum(tuple);
    2562                 :             : 
    2563                 :        1144 :                 pfree(values[0]);
    2564                 :             : 
    2565                 :             :                 /* mark entry as already visited */
    2566                 :        1144 :                 entry->ndoc = 0;
    2567                 :             : 
    2568                 :        1144 :                 return result;
    2569                 :        1144 :         }
    2570                 :             : 
    2571                 :           2 :         return (Datum) 0;
    2572                 :        1146 : }
    2573                 :             : 
    2574                 :             : static TSVectorStat *
    2575                 :           2 : ts_stat_sql(MemoryContext persistentContext, text *txt, text *ws)
    2576                 :             : {
    2577                 :           2 :         char       *query = text_to_cstring(txt);
    2578                 :           2 :         TSVectorStat *stat;
    2579                 :           2 :         bool            isnull;
    2580                 :           2 :         Portal          portal;
    2581                 :           2 :         SPIPlanPtr      plan;
    2582                 :             : 
    2583         [ +  - ]:           2 :         if ((plan = SPI_prepare(query, 0, NULL)) == NULL)
    2584                 :             :                 /* internal error */
    2585   [ #  #  #  # ]:           0 :                 elog(ERROR, "SPI_prepare(\"%s\") failed", query);
    2586                 :             : 
    2587         [ +  - ]:           2 :         if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL)
    2588                 :             :                 /* internal error */
    2589   [ #  #  #  # ]:           0 :                 elog(ERROR, "SPI_cursor_open(\"%s\") failed", query);
    2590                 :             : 
    2591                 :           2 :         SPI_cursor_fetch(portal, true, 100);
    2592                 :             : 
    2593         [ +  - ]:           2 :         if (SPI_tuptable == NULL ||
    2594                 :           2 :                 SPI_tuptable->tupdesc->natts != 1 ||
    2595                 :           2 :                 !IsBinaryCoercible(SPI_gettypeid(SPI_tuptable->tupdesc, 1),
    2596                 :             :                                                    TSVECTOROID))
    2597   [ #  #  #  # ]:           0 :                 ereport(ERROR,
    2598                 :             :                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
    2599                 :             :                                  errmsg("ts_stat query must return one tsvector column")));
    2600                 :             : 
    2601                 :           2 :         stat = MemoryContextAllocZero(persistentContext, sizeof(TSVectorStat));
    2602                 :           2 :         stat->maxdepth = 1;
    2603                 :             : 
    2604         [ +  + ]:           2 :         if (ws)
    2605                 :             :         {
    2606                 :           1 :                 char       *buf;
    2607                 :             : 
    2608                 :           1 :                 buf = VARDATA_ANY(ws);
    2609         [ +  + ]:           3 :                 while (buf - VARDATA_ANY(ws) < VARSIZE_ANY_EXHDR(ws))
    2610                 :             :                 {
    2611         [ -  + ]:           2 :                         if (pg_mblen(buf) == 1)
    2612                 :             :                         {
    2613   [ -  +  +  -  :           2 :                                 switch (*buf)
                      - ]
    2614                 :             :                                 {
    2615                 :             :                                         case 'A':
    2616                 :             :                                         case 'a':
    2617                 :           1 :                                                 stat->weight |= 1 << 3;
    2618                 :           1 :                                                 break;
    2619                 :             :                                         case 'B':
    2620                 :             :                                         case 'b':
    2621                 :           1 :                                                 stat->weight |= 1 << 2;
    2622                 :           1 :                                                 break;
    2623                 :             :                                         case 'C':
    2624                 :             :                                         case 'c':
    2625                 :           0 :                                                 stat->weight |= 1 << 1;
    2626                 :           0 :                                                 break;
    2627                 :             :                                         case 'D':
    2628                 :             :                                         case 'd':
    2629                 :           0 :                                                 stat->weight |= 1;
    2630                 :           0 :                                                 break;
    2631                 :             :                                         default:
    2632                 :           0 :                                                 stat->weight |= 0;
    2633                 :           0 :                                 }
    2634                 :           2 :                         }
    2635                 :           2 :                         buf += pg_mblen(buf);
    2636                 :             :                 }
    2637                 :           1 :         }
    2638                 :             : 
    2639         [ +  + ]:          14 :         while (SPI_processed > 0)
    2640                 :             :         {
    2641                 :          12 :                 uint64          i;
    2642                 :             : 
    2643         [ +  + ]:        1030 :                 for (i = 0; i < SPI_processed; i++)
    2644                 :             :                 {
    2645                 :        1018 :                         Datum           data = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull);
    2646                 :             : 
    2647         [ -  + ]:        1018 :                         if (!isnull)
    2648                 :        1018 :                                 stat = ts_accum(persistentContext, stat, data);
    2649                 :        1018 :                 }
    2650                 :             : 
    2651                 :          12 :                 SPI_freetuptable(SPI_tuptable);
    2652                 :          12 :                 SPI_cursor_fetch(portal, true, 100);
    2653                 :          12 :         }
    2654                 :             : 
    2655                 :           2 :         SPI_freetuptable(SPI_tuptable);
    2656                 :           2 :         SPI_cursor_close(portal);
    2657                 :           2 :         SPI_freeplan(plan);
    2658                 :           2 :         pfree(query);
    2659                 :             : 
    2660                 :           4 :         return stat;
    2661                 :           2 : }
    2662                 :             : 
    2663                 :             : Datum
    2664                 :        1144 : ts_stat1(PG_FUNCTION_ARGS)
    2665                 :             : {
    2666                 :        1144 :         FuncCallContext *funcctx;
    2667                 :        1144 :         Datum           result;
    2668                 :             : 
    2669         [ +  + ]:        1144 :         if (SRF_IS_FIRSTCALL())
    2670                 :             :         {
    2671                 :           1 :                 TSVectorStat *stat;
    2672                 :           1 :                 text       *txt = PG_GETARG_TEXT_PP(0);
    2673                 :             : 
    2674                 :           1 :                 funcctx = SRF_FIRSTCALL_INIT();
    2675                 :           1 :                 SPI_connect();
    2676                 :           1 :                 stat = ts_stat_sql(funcctx->multi_call_memory_ctx, txt, NULL);
    2677         [ +  - ]:           1 :                 PG_FREE_IF_COPY(txt, 0);
    2678                 :           1 :                 ts_setup_firstcall(fcinfo, funcctx, stat);
    2679                 :           1 :                 SPI_finish();
    2680                 :           1 :         }
    2681                 :             : 
    2682                 :        1144 :         funcctx = SRF_PERCALL_SETUP();
    2683         [ +  + ]:        1144 :         if ((result = ts_process_call(funcctx)) != (Datum) 0)
    2684                 :        1143 :                 SRF_RETURN_NEXT(funcctx, result);
    2685         [ +  - ]:           1 :         SRF_RETURN_DONE(funcctx);
    2686         [ -  + ]:        1144 : }
    2687                 :             : 
    2688                 :             : Datum
    2689                 :           2 : ts_stat2(PG_FUNCTION_ARGS)
    2690                 :             : {
    2691                 :           2 :         FuncCallContext *funcctx;
    2692                 :           2 :         Datum           result;
    2693                 :             : 
    2694         [ +  + ]:           2 :         if (SRF_IS_FIRSTCALL())
    2695                 :             :         {
    2696                 :           1 :                 TSVectorStat *stat;
    2697                 :           1 :                 text       *txt = PG_GETARG_TEXT_PP(0);
    2698                 :           1 :                 text       *ws = PG_GETARG_TEXT_PP(1);
    2699                 :             : 
    2700                 :           1 :                 funcctx = SRF_FIRSTCALL_INIT();
    2701                 :           1 :                 SPI_connect();
    2702                 :           1 :                 stat = ts_stat_sql(funcctx->multi_call_memory_ctx, txt, ws);
    2703         [ +  - ]:           1 :                 PG_FREE_IF_COPY(txt, 0);
    2704         [ +  - ]:           1 :                 PG_FREE_IF_COPY(ws, 1);
    2705                 :           1 :                 ts_setup_firstcall(fcinfo, funcctx, stat);
    2706                 :           1 :                 SPI_finish();
    2707                 :           1 :         }
    2708                 :             : 
    2709                 :           2 :         funcctx = SRF_PERCALL_SETUP();
    2710         [ +  + ]:           2 :         if ((result = ts_process_call(funcctx)) != (Datum) 0)
    2711                 :           1 :                 SRF_RETURN_NEXT(funcctx, result);
    2712         [ +  - ]:           1 :         SRF_RETURN_DONE(funcctx);
    2713         [ -  + ]:           2 : }
    2714                 :             : 
    2715                 :             : 
    2716                 :             : /*
    2717                 :             :  * Triggers for automatic update of a tsvector column from text column(s)
    2718                 :             :  *
    2719                 :             :  * Trigger arguments are either
    2720                 :             :  *              name of tsvector col, name of tsconfig to use, name(s) of text col(s)
    2721                 :             :  *              name of tsvector col, name of regconfig col, name(s) of text col(s)
    2722                 :             :  * ie, tsconfig can either be specified by name, or indirectly as the
    2723                 :             :  * contents of a regconfig field in the row.  If the name is used, it must
    2724                 :             :  * be explicitly schema-qualified.
    2725                 :             :  */
    2726                 :             : Datum
    2727                 :           3 : tsvector_update_trigger_byid(PG_FUNCTION_ARGS)
    2728                 :             : {
    2729                 :           3 :         return tsvector_update_trigger(fcinfo, false);
    2730                 :             : }
    2731                 :             : 
    2732                 :             : Datum
    2733                 :           0 : tsvector_update_trigger_bycolumn(PG_FUNCTION_ARGS)
    2734                 :             : {
    2735                 :           0 :         return tsvector_update_trigger(fcinfo, true);
    2736                 :             : }
    2737                 :             : 
    2738                 :             : static Datum
    2739                 :           3 : tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column)
    2740                 :             : {
    2741                 :           3 :         TriggerData *trigdata;
    2742                 :           3 :         Trigger    *trigger;
    2743                 :           3 :         Relation        rel;
    2744                 :           3 :         HeapTuple       rettuple = NULL;
    2745                 :           3 :         int                     tsvector_attr_num,
    2746                 :             :                                 i;
    2747                 :           3 :         ParsedText      prs;
    2748                 :           3 :         Datum           datum;
    2749                 :           3 :         bool            isnull;
    2750                 :           3 :         text       *txt;
    2751                 :           3 :         Oid                     cfgId;
    2752                 :           3 :         bool            update_needed;
    2753                 :             : 
    2754                 :             :         /* Check call context */
    2755         [ +  - ]:           3 :         if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */
    2756   [ #  #  #  # ]:           0 :                 elog(ERROR, "tsvector_update_trigger: not fired by trigger manager");
    2757                 :             : 
    2758                 :           3 :         trigdata = (TriggerData *) fcinfo->context;
    2759         [ +  - ]:           3 :         if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
    2760   [ #  #  #  # ]:           0 :                 elog(ERROR, "tsvector_update_trigger: must be fired for row");
    2761         [ +  - ]:           3 :         if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
    2762   [ #  #  #  # ]:           0 :                 elog(ERROR, "tsvector_update_trigger: must be fired BEFORE event");
    2763                 :             : 
    2764         [ +  + ]:           3 :         if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
    2765                 :             :         {
    2766                 :           2 :                 rettuple = trigdata->tg_trigtuple;
    2767                 :           2 :                 update_needed = true;
    2768                 :           2 :         }
    2769         [ +  - ]:           1 :         else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
    2770                 :             :         {
    2771                 :           1 :                 rettuple = trigdata->tg_newtuple;
    2772                 :           1 :                 update_needed = false;  /* computed below */
    2773                 :           1 :         }
    2774                 :             :         else
    2775   [ #  #  #  # ]:           0 :                 elog(ERROR, "tsvector_update_trigger: must be fired for INSERT or UPDATE");
    2776                 :             : 
    2777                 :           3 :         trigger = trigdata->tg_trigger;
    2778                 :           3 :         rel = trigdata->tg_relation;
    2779                 :             : 
    2780         [ +  - ]:           3 :         if (trigger->tgnargs < 3)
    2781   [ #  #  #  # ]:           0 :                 elog(ERROR, "tsvector_update_trigger: arguments must be tsvector_field, ts_config, text_field1, ...)");
    2782                 :             : 
    2783                 :             :         /* Find the target tsvector column */
    2784                 :           3 :         tsvector_attr_num = SPI_fnumber(rel->rd_att, trigger->tgargs[0]);
    2785         [ +  - ]:           3 :         if (tsvector_attr_num == SPI_ERROR_NOATTRIBUTE)
    2786   [ #  #  #  # ]:           0 :                 ereport(ERROR,
    2787                 :             :                                 (errcode(ERRCODE_UNDEFINED_COLUMN),
    2788                 :             :                                  errmsg("tsvector column \"%s\" does not exist",
    2789                 :             :                                                 trigger->tgargs[0])));
    2790                 :             :         /* This will effectively reject system columns, so no separate test: */
    2791         [ +  - ]:           3 :         if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, tsvector_attr_num),
    2792                 :             :                                                    TSVECTOROID))
    2793   [ #  #  #  # ]:           0 :                 ereport(ERROR,
    2794                 :             :                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
    2795                 :             :                                  errmsg("column \"%s\" is not of tsvector type",
    2796                 :             :                                                 trigger->tgargs[0])));
    2797                 :             : 
    2798                 :             :         /* Find the configuration to use */
    2799         [ -  + ]:           3 :         if (config_column)
    2800                 :             :         {
    2801                 :           0 :                 int                     config_attr_num;
    2802                 :             : 
    2803                 :           0 :                 config_attr_num = SPI_fnumber(rel->rd_att, trigger->tgargs[1]);
    2804         [ #  # ]:           0 :                 if (config_attr_num == SPI_ERROR_NOATTRIBUTE)
    2805   [ #  #  #  # ]:           0 :                         ereport(ERROR,
    2806                 :             :                                         (errcode(ERRCODE_UNDEFINED_COLUMN),
    2807                 :             :                                          errmsg("configuration column \"%s\" does not exist",
    2808                 :             :                                                         trigger->tgargs[1])));
    2809         [ #  # ]:           0 :                 if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, config_attr_num),
    2810                 :             :                                                            REGCONFIGOID))
    2811   [ #  #  #  # ]:           0 :                         ereport(ERROR,
    2812                 :             :                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
    2813                 :             :                                          errmsg("column \"%s\" is not of regconfig type",
    2814                 :             :                                                         trigger->tgargs[1])));
    2815                 :             : 
    2816                 :           0 :                 datum = SPI_getbinval(rettuple, rel->rd_att, config_attr_num, &isnull);
    2817         [ #  # ]:           0 :                 if (isnull)
    2818   [ #  #  #  # ]:           0 :                         ereport(ERROR,
    2819                 :             :                                         (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
    2820                 :             :                                          errmsg("configuration column \"%s\" must not be null",
    2821                 :             :                                                         trigger->tgargs[1])));
    2822                 :           0 :                 cfgId = DatumGetObjectId(datum);
    2823                 :           0 :         }
    2824                 :             :         else
    2825                 :             :         {
    2826                 :           3 :                 List       *names;
    2827                 :             : 
    2828                 :           3 :                 names = stringToQualifiedNameList(trigger->tgargs[1], NULL);
    2829                 :             :                 /* require a schema so that results are not search path dependent */
    2830         [ +  - ]:           3 :                 if (list_length(names) < 2)
    2831   [ #  #  #  # ]:           0 :                         ereport(ERROR,
    2832                 :             :                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
    2833                 :             :                                          errmsg("text search configuration name \"%s\" must be schema-qualified",
    2834                 :             :                                                         trigger->tgargs[1])));
    2835                 :           3 :                 cfgId = get_ts_config_oid(names, false);
    2836                 :           3 :         }
    2837                 :             : 
    2838                 :             :         /* initialize parse state */
    2839                 :           3 :         prs.lenwords = 32;
    2840                 :           3 :         prs.curwords = 0;
    2841                 :           3 :         prs.pos = 0;
    2842                 :           3 :         prs.words = palloc_array(ParsedWord, prs.lenwords);
    2843                 :             : 
    2844                 :             :         /* find all words in indexable column(s) */
    2845         [ +  + ]:           6 :         for (i = 2; i < trigger->tgnargs; i++)
    2846                 :             :         {
    2847                 :           3 :                 int                     numattr;
    2848                 :             : 
    2849                 :           3 :                 numattr = SPI_fnumber(rel->rd_att, trigger->tgargs[i]);
    2850         [ +  - ]:           3 :                 if (numattr == SPI_ERROR_NOATTRIBUTE)
    2851   [ #  #  #  # ]:           0 :                         ereport(ERROR,
    2852                 :             :                                         (errcode(ERRCODE_UNDEFINED_COLUMN),
    2853                 :             :                                          errmsg("column \"%s\" does not exist",
    2854                 :             :                                                         trigger->tgargs[i])));
    2855         [ +  - ]:           3 :                 if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, numattr), TEXTOID))
    2856   [ #  #  #  # ]:           0 :                         ereport(ERROR,
    2857                 :             :                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
    2858                 :             :                                          errmsg("column \"%s\" is not of a character type",
    2859                 :             :                                                         trigger->tgargs[i])));
    2860                 :             : 
    2861         [ +  + ]:           3 :                 if (bms_is_member(numattr - FirstLowInvalidHeapAttributeNumber, trigdata->tg_updatedcols))
    2862                 :           1 :                         update_needed = true;
    2863                 :             : 
    2864                 :           3 :                 datum = SPI_getbinval(rettuple, rel->rd_att, numattr, &isnull);
    2865         [ +  + ]:           3 :                 if (isnull)
    2866                 :           1 :                         continue;
    2867                 :             : 
    2868                 :           2 :                 txt = DatumGetTextPP(datum);
    2869                 :             : 
    2870                 :           2 :                 parsetext(cfgId, &prs, VARDATA_ANY(txt), VARSIZE_ANY_EXHDR(txt));
    2871                 :             : 
    2872         [ +  - ]:           2 :                 if (txt != (text *) DatumGetPointer(datum))
    2873                 :           0 :                         pfree(txt);
    2874      [ -  +  + ]:           3 :         }
    2875                 :             : 
    2876         [ -  + ]:           3 :         if (update_needed)
    2877                 :             :         {
    2878                 :             :                 /* make tsvector value */
    2879                 :           3 :                 datum = TSVectorGetDatum(make_tsvector(&prs));
    2880                 :           3 :                 isnull = false;
    2881                 :             : 
    2882                 :             :                 /* and insert it into tuple */
    2883                 :           3 :                 rettuple = heap_modify_tuple_by_cols(rettuple, rel->rd_att,
    2884                 :             :                                                                                          1, &tsvector_attr_num,
    2885                 :             :                                                                                          &datum, &isnull);
    2886                 :             : 
    2887                 :           3 :                 pfree(DatumGetPointer(datum));
    2888                 :           3 :         }
    2889                 :             : 
    2890                 :           6 :         return PointerGetDatum(rettuple);
    2891                 :           3 : }
        

Generated by: LCOV version 2.3.2-1