LCOV - code coverage report
Current view: top level - src/backend/utils/adt - tsrank.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 73.1 % 435 318
Test Date: 2026-05-22 00:16:36 Functions: 66.7 % 24 16
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * tsrank.c
       4              :  *      rank tsvector by tsquery
       5              :  *
       6              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7              :  *
       8              :  *
       9              :  * IDENTIFICATION
      10              :  *    src/backend/utils/adt/tsrank.c
      11              :  *
      12              :  *-------------------------------------------------------------------------
      13              :  */
      14              : #include "postgres.h"
      15              : 
      16              : #include <limits.h>
      17              : #include <math.h>
      18              : 
      19              : #include "miscadmin.h"
      20              : #include "tsearch/ts_utils.h"
      21              : #include "utils/array.h"
      22              : #include "utils/fmgrprotos.h"
      23              : 
      24              : #define NUM_WEIGHTS 4
      25              : static const float default_weights[NUM_WEIGHTS] = {0.1f, 0.2f, 0.4f, 1.0f};
      26              : 
      27              : #define wpos(wep)   ( w[ WEP_GETWEIGHT(wep) ] )
      28              : 
      29              : #define RANK_NO_NORM            0x00
      30              : #define RANK_NORM_LOGLENGTH     0x01
      31              : #define RANK_NORM_LENGTH        0x02
      32              : #define RANK_NORM_EXTDIST       0x04
      33              : #define RANK_NORM_UNIQ          0x08
      34              : #define RANK_NORM_LOGUNIQ       0x10
      35              : #define RANK_NORM_RDIVRPLUS1    0x20
      36              : #define DEF_NORM_METHOD         RANK_NO_NORM
      37              : 
      38              : static float calc_rank_or(const float *w, TSVector t, TSQuery q);
      39              : static float calc_rank_and(const float *w, TSVector t, TSQuery q);
      40              : 
      41              : /*
      42              :  * Returns a weight of a word collocation
      43              :  */
      44              : static float4
      45           15 : word_distance(int32 w)
      46              : {
      47           15 :     if (w > 100)
      48            0 :         return 1e-30f;
      49              : 
      50           15 :     return 1.0 / (1.005 + 0.05 * exp(((float4) w) / 1.5 - 2));
      51              : }
      52              : 
      53              : static int
      54            0 : cnt_length(TSVector t)
      55              : {
      56            0 :     WordEntry  *ptr = ARRPTR(t),
      57            0 :                *end = (WordEntry *) STRPTR(t);
      58            0 :     int         len = 0;
      59              : 
      60            0 :     while (ptr < end)
      61              :     {
      62            0 :         int         clen = POSDATALEN(t, ptr);
      63              : 
      64            0 :         if (clen == 0)
      65            0 :             len += 1;
      66              :         else
      67            0 :             len += clen;
      68              : 
      69            0 :         ptr++;
      70              :     }
      71              : 
      72            0 :     return len;
      73              : }
      74              : 
      75              : 
      76              : #define WordECompareQueryItem(e,q,p,i,m) \
      77              :     tsCompareString((q) + (i)->distance, (i)->length, \
      78              :                     (e) + (p)->pos, (p)->len, (m))
      79              : 
      80              : 
      81              : /*
      82              :  * Returns a pointer to a WordEntry's array corresponding to 'item' from
      83              :  * tsvector 't'. 'q' is the TSQuery containing 'item'.
      84              :  * Returns NULL if not found.
      85              :  */
      86              : static WordEntry *
      87          352 : find_wordentry(TSVector t, TSQuery q, QueryOperand *item, int32 *nitem)
      88              : {
      89          352 :     WordEntry  *StopLow = ARRPTR(t);
      90          352 :     WordEntry  *StopHigh = (WordEntry *) STRPTR(t);
      91          352 :     WordEntry  *StopMiddle = StopHigh;
      92              :     int         difference;
      93              : 
      94          352 :     *nitem = 0;
      95              : 
      96              :     /* Loop invariant: StopLow <= item < StopHigh */
      97          951 :     while (StopLow < StopHigh)
      98              :     {
      99          911 :         StopMiddle = StopLow + (StopHigh - StopLow) / 2;
     100          911 :         difference = WordECompareQueryItem(STRPTR(t), GETOPERAND(q), StopMiddle, item, false);
     101          911 :         if (difference == 0)
     102              :         {
     103          312 :             StopHigh = StopMiddle;
     104          312 :             *nitem = 1;
     105          312 :             break;
     106              :         }
     107          599 :         else if (difference > 0)
     108          210 :             StopLow = StopMiddle + 1;
     109              :         else
     110          389 :             StopHigh = StopMiddle;
     111              :     }
     112              : 
     113          352 :     if (item->prefix)
     114              :     {
     115           45 :         if (StopLow >= StopHigh)
     116           45 :             StopMiddle = StopHigh;
     117              : 
     118           45 :         *nitem = 0;
     119              : 
     120          185 :         while (StopMiddle < (WordEntry *) STRPTR(t) &&
     121           70 :                WordECompareQueryItem(STRPTR(t), GETOPERAND(q), StopMiddle, item, true) == 0)
     122              :         {
     123           70 :             (*nitem)++;
     124           70 :             StopMiddle++;
     125              :         }
     126              :     }
     127              : 
     128          352 :     return (*nitem > 0) ? StopHigh : NULL;
     129              : }
     130              : 
     131              : 
     132              : /*
     133              :  * sort QueryOperands by (length, word)
     134              :  */
     135              : static int
     136           90 : compareQueryOperand(const void *a, const void *b, void *arg)
     137              : {
     138           90 :     char       *operand = (char *) arg;
     139           90 :     QueryOperand *qa = (*(QueryOperand *const *) a);
     140           90 :     QueryOperand *qb = (*(QueryOperand *const *) b);
     141              : 
     142          180 :     return tsCompareString(operand + qa->distance, qa->length,
     143           90 :                            operand + qb->distance, qb->length,
     144              :                            false);
     145              : }
     146              : 
     147              : /*
     148              :  * Returns a sorted, de-duplicated array of QueryOperands in a query.
     149              :  * The returned QueryOperands are pointers to the original QueryOperands
     150              :  * in the query.
     151              :  *
     152              :  * Length of the returned array is stored in *size
     153              :  */
     154              : static QueryOperand **
     155           45 : SortAndUniqItems(TSQuery q, int *size)
     156              : {
     157           45 :     char       *operand = GETOPERAND(q);
     158           45 :     QueryItem  *item = GETQUERY(q);
     159              :     QueryOperand **res,
     160              :               **ptr,
     161              :               **prevptr;
     162              : 
     163           45 :     ptr = res = palloc_array(QueryOperand *, *size);
     164              : 
     165              :     /* Collect all operands from the tree to res */
     166          180 :     while ((*size)--)
     167              :     {
     168          135 :         if (item->type == QI_VAL)
     169              :         {
     170           90 :             *ptr = (QueryOperand *) item;
     171           90 :             ptr++;
     172              :         }
     173          135 :         item++;
     174              :     }
     175              : 
     176           45 :     *size = ptr - res;
     177           45 :     if (*size < 2)
     178            0 :         return res;
     179              : 
     180           45 :     qsort_arg(res, *size, sizeof(QueryOperand *), compareQueryOperand, operand);
     181              : 
     182           45 :     ptr = res + 1;
     183           45 :     prevptr = res;
     184              : 
     185              :     /* remove duplicates */
     186           90 :     while (ptr - res < *size)
     187              :     {
     188           45 :         if (compareQueryOperand(ptr, prevptr, operand) != 0)
     189              :         {
     190           45 :             prevptr++;
     191           45 :             *prevptr = *ptr;
     192              :         }
     193           45 :         ptr++;
     194              :     }
     195              : 
     196           45 :     *size = prevptr + 1 - res;
     197           45 :     return res;
     198              : }
     199              : 
     200              : static float
     201           15 : calc_rank_and(const float *w, TSVector t, TSQuery q)
     202              : {
     203              :     WordEntryPosVector **pos;
     204              :     WordEntryPosVector1 posnull;
     205              :     WordEntryPosVector *POSNULL;
     206              :     int         i,
     207              :                 k,
     208              :                 l,
     209              :                 p;
     210              :     WordEntry  *entry,
     211              :                *firstentry;
     212              :     WordEntryPos *post,
     213              :                *ct;
     214              :     int32       dimt,
     215              :                 lenct,
     216              :                 dist,
     217              :                 nitem;
     218           15 :     float       res = -1.0;
     219              :     QueryOperand **item;
     220           15 :     int         size = q->size;
     221              : 
     222           15 :     item = SortAndUniqItems(q, &size);
     223           15 :     if (size < 2)
     224              :     {
     225            0 :         pfree(item);
     226            0 :         return calc_rank_or(w, t, q);
     227              :     }
     228           15 :     pos = palloc0_array(WordEntryPosVector *, q->size);
     229              : 
     230              :     /* A dummy WordEntryPos array to use when haspos is false */
     231           15 :     posnull.npos = 1;
     232           15 :     posnull.pos[0] = 0;
     233           15 :     WEP_SETPOS(posnull.pos[0], MAXENTRYPOS - 1);
     234           15 :     POSNULL = (WordEntryPosVector *) &posnull;
     235              : 
     236           45 :     for (i = 0; i < size; i++)
     237              :     {
     238           30 :         firstentry = entry = find_wordentry(t, q, item[i], &nitem);
     239           30 :         if (!entry)
     240            0 :             continue;
     241              : 
     242           60 :         while (entry - firstentry < nitem)
     243              :         {
     244           30 :             if (entry->haspos)
     245           30 :                 pos[i] = _POSVECPTR(t, entry);
     246              :             else
     247            0 :                 pos[i] = POSNULL;
     248              : 
     249           30 :             dimt = pos[i]->npos;
     250           30 :             post = pos[i]->pos;
     251           45 :             for (k = 0; k < i; k++)
     252              :             {
     253           15 :                 if (!pos[k])
     254            0 :                     continue;
     255           15 :                 lenct = pos[k]->npos;
     256           15 :                 ct = pos[k]->pos;
     257           30 :                 for (l = 0; l < dimt; l++)
     258              :                 {
     259           30 :                     for (p = 0; p < lenct; p++)
     260              :                     {
     261           15 :                         dist = abs((int) WEP_GETPOS(post[l]) - (int) WEP_GETPOS(ct[p]));
     262           15 :                         if (dist || (dist == 0 && (pos[i] == POSNULL || pos[k] == POSNULL)))
     263              :                         {
     264              :                             float       curw;
     265              : 
     266           15 :                             if (!dist)
     267            0 :                                 dist = MAXENTRYPOS;
     268           15 :                             curw = sqrt(wpos(post[l]) * wpos(ct[p]) * word_distance(dist));
     269           15 :                             res = (res < 0) ? curw : 1.0 - (1.0 - res) * (1.0 - curw);
     270              :                         }
     271              :                     }
     272              :                 }
     273              :             }
     274              : 
     275           30 :             entry++;
     276              :         }
     277              :     }
     278           15 :     pfree(pos);
     279           15 :     pfree(item);
     280           15 :     return res;
     281              : }
     282              : 
     283              : static float
     284           30 : calc_rank_or(const float *w, TSVector t, TSQuery q)
     285              : {
     286              :     WordEntry  *entry,
     287              :                *firstentry;
     288              :     WordEntryPosVector1 posnull;
     289              :     WordEntryPos *post;
     290              :     int32       dimt,
     291              :                 j,
     292              :                 i,
     293              :                 nitem;
     294           30 :     float       res = 0.0;
     295              :     QueryOperand **item;
     296           30 :     int         size = q->size;
     297              : 
     298              :     /* A dummy WordEntryPos array to use when haspos is false */
     299           30 :     posnull.npos = 1;
     300           30 :     posnull.pos[0] = 0;
     301              : 
     302           30 :     item = SortAndUniqItems(q, &size);
     303              : 
     304           90 :     for (i = 0; i < size; i++)
     305              :     {
     306              :         float       resj,
     307              :                     wjm;
     308              :         int32       jm;
     309              : 
     310           60 :         firstentry = entry = find_wordentry(t, q, item[i], &nitem);
     311           60 :         if (!entry)
     312            5 :             continue;
     313              : 
     314          110 :         while (entry - firstentry < nitem)
     315              :         {
     316              :             /* Identify the weight data to use */
     317           55 :             if (entry->haspos)
     318              :             {
     319           55 :                 dimt = POSDATALEN(t, entry);
     320           55 :                 post = POSDATAPTR(t, entry);
     321              :             }
     322              :             else
     323              :             {
     324            0 :                 dimt = posnull.npos;
     325            0 :                 post = posnull.pos;
     326              :             }
     327              : 
     328              :             /*
     329              :              * Compute the score for this term, then add it to "res".
     330              :              *
     331              :              * The ideal score for a term is the weighted harmonic sum:
     332              :              *
     333              :              * resj = sum(wi / i^2, i = 1..noccurrences)
     334              :              *
     335              :              * where wi is the weight of the i-th occurrence and weights are
     336              :              * sorted in descending order so that the highest-weight
     337              :              * occurrence gets the smallest divisor (i=1) and thus contributes
     338              :              * the most.
     339              :              *
     340              :              * This result should be divided by pi^2/6 ~= 1.64493406685, which
     341              :              * is the limit of sum(1/i^2, i=1..inf). This normalizes the score
     342              :              * to the [0, 1] range.
     343              :              *
     344              :              * As an approximation for efficiency, we skip doing a sort and
     345              :              * instead just promote the single highest-weight occurrence to
     346              :              * position i=1. This is done by taking the raw (unsorted) sum
     347              :              * resj, subtracting the maximum weight's actual contribution
     348              :              * wjm/(jm+1)^2, and adding back its corrected contribution
     349              :              * wjm/1^2 = wjm.
     350              :              *
     351              :              * The remaining occurrences are left in their original order.
     352              :              */
     353              : 
     354              :             /* calculate harmonic sum without re-ordering */
     355           55 :             resj = 0.0;
     356           55 :             wjm = -1.0;
     357           55 :             jm = 0;
     358          110 :             for (j = 0; j < dimt; j++)
     359              :             {
     360           55 :                 resj = resj + wpos(post[j]) / ((j + 1) * (j + 1));
     361           55 :                 if (wpos(post[j]) > wjm)
     362              :                 {
     363           55 :                     wjm = wpos(post[j]);
     364           55 :                     jm = j;
     365              :                 }
     366              :             }
     367              : 
     368              :             /* apply correction as explained above, then add to res */
     369           55 :             res = res + (resj - wjm / ((jm + 1) * (jm + 1)) + wjm) / 1.64493406685;
     370              : 
     371           55 :             entry++;
     372              :         }
     373              :     }
     374           30 :     if (size > 0)
     375           30 :         res = res / size;
     376           30 :     pfree(item);
     377           30 :     return res;
     378              : }
     379              : 
     380              : static float
     381           45 : calc_rank(const float *w, TSVector t, TSQuery q, int32 method)
     382              : {
     383           45 :     QueryItem  *item = GETQUERY(q);
     384           45 :     float       res = 0.0;
     385              :     int         len;
     386              : 
     387           45 :     if (!t->size || !q->size)
     388            0 :         return 0.0;
     389              : 
     390              :     /* XXX: What about NOT? */
     391           45 :     res = (item->type == QI_OPR && (item->qoperator.oper == OP_AND ||
     392           30 :                                     item->qoperator.oper == OP_PHRASE)) ?
     393           60 :         calc_rank_and(w, t, q) :
     394           30 :         calc_rank_or(w, t, q);
     395              : 
     396           45 :     if (res < 0)
     397            0 :         res = 1e-20f;
     398              : 
     399           45 :     if ((method & RANK_NORM_LOGLENGTH) && t->size > 0)
     400            0 :         res /= log((double) (cnt_length(t) + 1)) / log(2.0);
     401              : 
     402           45 :     if (method & RANK_NORM_LENGTH)
     403              :     {
     404            0 :         len = cnt_length(t);
     405            0 :         if (len > 0)
     406            0 :             res /= (float) len;
     407              :     }
     408              : 
     409              :     /* RANK_NORM_EXTDIST not applicable */
     410              : 
     411           45 :     if ((method & RANK_NORM_UNIQ) && t->size > 0)
     412            0 :         res /= (float) (t->size);
     413              : 
     414           45 :     if ((method & RANK_NORM_LOGUNIQ) && t->size > 0)
     415            0 :         res /= log((double) (t->size + 1)) / log(2.0);
     416              : 
     417           45 :     if (method & RANK_NORM_RDIVRPLUS1)
     418            0 :         res /= (res + 1);
     419              : 
     420           45 :     return res;
     421              : }
     422              : 
     423              : /*
     424              :  * Extract weights from an array. The weights are stored in *ws, which must
     425              :  * have space for NUM_WEIGHTS elements.
     426              :  */
     427              : static void
     428            0 : getWeights(ArrayType *win, float *ws)
     429              : {
     430              :     int         i;
     431              :     float4     *arrdata;
     432              : 
     433              :     Assert(win != NULL);
     434              : 
     435            0 :     if (ARR_NDIM(win) != 1)
     436            0 :         ereport(ERROR,
     437              :                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
     438              :                  errmsg("array of weight must be one-dimensional")));
     439              : 
     440            0 :     if (ArrayGetNItems(ARR_NDIM(win), ARR_DIMS(win)) < NUM_WEIGHTS)
     441            0 :         ereport(ERROR,
     442              :                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
     443              :                  errmsg("array of weight is too short")));
     444              : 
     445            0 :     if (array_contains_nulls(win))
     446            0 :         ereport(ERROR,
     447              :                 (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
     448              :                  errmsg("array of weight must not contain nulls")));
     449              : 
     450            0 :     arrdata = (float4 *) ARR_DATA_PTR(win);
     451            0 :     for (i = 0; i < NUM_WEIGHTS; i++)
     452              :     {
     453            0 :         ws[i] = (arrdata[i] >= 0) ? arrdata[i] : default_weights[i];
     454            0 :         if (ws[i] > 1.0)
     455            0 :             ereport(ERROR,
     456              :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     457              :                      errmsg("weight out of range")));
     458              :     }
     459            0 : }
     460              : 
     461              : Datum
     462            0 : ts_rank_wttf(PG_FUNCTION_ARGS)
     463              : {
     464            0 :     ArrayType  *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
     465            0 :     TSVector    txt = PG_GETARG_TSVECTOR(1);
     466            0 :     TSQuery     query = PG_GETARG_TSQUERY(2);
     467            0 :     int         method = PG_GETARG_INT32(3);
     468              :     float       weights[NUM_WEIGHTS];
     469              :     float       res;
     470              : 
     471            0 :     getWeights(win, weights);
     472            0 :     res = calc_rank(weights, txt, query, method);
     473              : 
     474            0 :     PG_FREE_IF_COPY(win, 0);
     475            0 :     PG_FREE_IF_COPY(txt, 1);
     476            0 :     PG_FREE_IF_COPY(query, 2);
     477            0 :     PG_RETURN_FLOAT4(res);
     478              : }
     479              : 
     480              : Datum
     481            0 : ts_rank_wtt(PG_FUNCTION_ARGS)
     482              : {
     483            0 :     ArrayType  *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
     484            0 :     TSVector    txt = PG_GETARG_TSVECTOR(1);
     485            0 :     TSQuery     query = PG_GETARG_TSQUERY(2);
     486              :     float       weights[NUM_WEIGHTS];
     487              :     float       res;
     488              : 
     489            0 :     getWeights(win, weights);
     490            0 :     res = calc_rank(weights, txt, query, DEF_NORM_METHOD);
     491              : 
     492            0 :     PG_FREE_IF_COPY(win, 0);
     493            0 :     PG_FREE_IF_COPY(txt, 1);
     494            0 :     PG_FREE_IF_COPY(query, 2);
     495            0 :     PG_RETURN_FLOAT4(res);
     496              : }
     497              : 
     498              : Datum
     499            0 : ts_rank_ttf(PG_FUNCTION_ARGS)
     500              : {
     501            0 :     TSVector    txt = PG_GETARG_TSVECTOR(0);
     502            0 :     TSQuery     query = PG_GETARG_TSQUERY(1);
     503            0 :     int         method = PG_GETARG_INT32(2);
     504              :     float       res;
     505              : 
     506            0 :     res = calc_rank(default_weights, txt, query, method);
     507              : 
     508            0 :     PG_FREE_IF_COPY(txt, 0);
     509            0 :     PG_FREE_IF_COPY(query, 1);
     510            0 :     PG_RETURN_FLOAT4(res);
     511              : }
     512              : 
     513              : Datum
     514           45 : ts_rank_tt(PG_FUNCTION_ARGS)
     515              : {
     516           45 :     TSVector    txt = PG_GETARG_TSVECTOR(0);
     517           45 :     TSQuery     query = PG_GETARG_TSQUERY(1);
     518              :     float       res;
     519              : 
     520           45 :     res = calc_rank(default_weights, txt, query, DEF_NORM_METHOD);
     521              : 
     522           45 :     PG_FREE_IF_COPY(txt, 0);
     523           45 :     PG_FREE_IF_COPY(query, 1);
     524           45 :     PG_RETURN_FLOAT4(res);
     525              : }
     526              : 
     527              : typedef struct
     528              : {
     529              :     union
     530              :     {
     531              :         struct
     532              :         {                       /* compiled doc representation */
     533              :             QueryItem **items;
     534              :             int16       nitem;
     535              :         }           query;
     536              :         struct
     537              :         {                       /* struct is used for preparing doc
     538              :                                  * representation */
     539              :             QueryItem  *item;
     540              :             WordEntry  *entry;
     541              :         }           map;
     542              :     }           data;
     543              :     WordEntryPos pos;
     544              : } DocRepresentation;
     545              : 
     546              : static int
     547          290 : compareDocR(const void *va, const void *vb)
     548              : {
     549          290 :     const DocRepresentation *a = (const DocRepresentation *) va;
     550          290 :     const DocRepresentation *b = (const DocRepresentation *) vb;
     551              : 
     552          290 :     if (WEP_GETPOS(a->pos) == WEP_GETPOS(b->pos))
     553              :     {
     554           30 :         if (WEP_GETWEIGHT(a->pos) == WEP_GETWEIGHT(b->pos))
     555              :         {
     556            5 :             if (a->data.map.entry == b->data.map.entry)
     557            5 :                 return 0;
     558              : 
     559            0 :             return (a->data.map.entry > b->data.map.entry) ? 1 : -1;
     560              :         }
     561              : 
     562           25 :         return (WEP_GETWEIGHT(a->pos) > WEP_GETWEIGHT(b->pos)) ? 1 : -1;
     563              :     }
     564              : 
     565          260 :     return (WEP_GETPOS(a->pos) > WEP_GETPOS(b->pos)) ? 1 : -1;
     566              : }
     567              : 
     568              : #define MAXQROPOS   MAXENTRYPOS
     569              : typedef struct
     570              : {
     571              :     bool        operandexists;
     572              :     bool        reverseinsert;  /* indicates insert order, true means
     573              :                                  * descending order */
     574              :     uint32      npos;
     575              :     WordEntryPos pos[MAXQROPOS];
     576              : } QueryRepresentationOperand;
     577              : 
     578              : typedef struct
     579              : {
     580              :     TSQuery     query;
     581              :     QueryRepresentationOperand *operandData;
     582              : } QueryRepresentation;
     583              : 
     584              : #define QR_GET_OPERAND_DATA(q, v) \
     585              :     ( (q)->operandData + (((QueryItem*)(v)) - GETQUERY((q)->query)) )
     586              : 
     587              : /*
     588              :  * TS_execute callback for matching a tsquery operand to QueryRepresentation
     589              :  */
     590              : static TSTernaryValue
     591          963 : checkcondition_QueryOperand(void *checkval, QueryOperand *val,
     592              :                             ExecPhraseData *data)
     593              : {
     594          963 :     QueryRepresentation *qr = (QueryRepresentation *) checkval;
     595          963 :     QueryRepresentationOperand *opData = QR_GET_OPERAND_DATA(qr, val);
     596              : 
     597          963 :     if (!opData->operandexists)
     598          369 :         return TS_NO;
     599              : 
     600          594 :     if (data)
     601              :     {
     602          290 :         data->npos = opData->npos;
     603          290 :         data->pos = opData->pos;
     604          290 :         if (opData->reverseinsert)
     605           90 :             data->pos += MAXQROPOS - opData->npos;
     606              :     }
     607              : 
     608          594 :     return TS_YES;
     609              : }
     610              : 
     611              : typedef struct
     612              : {
     613              :     int         pos;
     614              :     int         p;
     615              :     int         q;
     616              :     DocRepresentation *begin;
     617              :     DocRepresentation *end;
     618              : } CoverExt;
     619              : 
     620              : static void
     621          414 : resetQueryRepresentation(QueryRepresentation *qr, bool reverseinsert)
     622              : {
     623              :     int         i;
     624              : 
     625         1676 :     for (i = 0; i < qr->query->size; i++)
     626              :     {
     627         1262 :         qr->operandData[i].operandexists = false;
     628         1262 :         qr->operandData[i].reverseinsert = reverseinsert;
     629         1262 :         qr->operandData[i].npos = 0;
     630              :     }
     631          414 : }
     632              : 
     633              : static void
     634          599 : fillQueryRepresentationData(QueryRepresentation *qr, DocRepresentation *entry)
     635              : {
     636              :     int         i;
     637              :     int         lastPos;
     638              :     QueryRepresentationOperand *opData;
     639              : 
     640         1203 :     for (i = 0; i < entry->data.query.nitem; i++)
     641              :     {
     642          604 :         if (entry->data.query.items[i]->type != QI_VAL)
     643            0 :             continue;
     644              : 
     645          604 :         opData = QR_GET_OPERAND_DATA(qr, entry->data.query.items[i]);
     646              : 
     647          604 :         opData->operandexists = true;
     648              : 
     649          604 :         if (opData->npos == 0)
     650              :         {
     651          549 :             lastPos = (opData->reverseinsert) ? (MAXQROPOS - 1) : 0;
     652          549 :             opData->pos[lastPos] = entry->pos;
     653          549 :             opData->npos++;
     654          549 :             continue;
     655              :         }
     656              : 
     657          110 :         lastPos = opData->reverseinsert ?
     658           55 :             (MAXQROPOS - opData->npos) :
     659           55 :             (opData->npos - 1);
     660              : 
     661           55 :         if (WEP_GETPOS(opData->pos[lastPos]) != WEP_GETPOS(entry->pos))
     662              :         {
     663           70 :             lastPos = opData->reverseinsert ?
     664           35 :                 (MAXQROPOS - 1 - opData->npos) :
     665           35 :                 (opData->npos);
     666              : 
     667           35 :             opData->pos[lastPos] = entry->pos;
     668           35 :             opData->npos++;
     669              :         }
     670              :     }
     671          599 : }
     672              : 
     673              : static bool
     674          269 : Cover(DocRepresentation *doc, int len, QueryRepresentation *qr, CoverExt *ext)
     675              : {
     676              :     DocRepresentation *ptr;
     677          269 :     int         lastpos = ext->pos;
     678          269 :     bool        found = false;
     679              : 
     680              :     /*
     681              :      * since this function recurses, it could be driven to stack overflow.
     682              :      * (though any decent compiler will optimize away the tail-recursion.
     683              :      */
     684          269 :     check_stack_depth();
     685              : 
     686          269 :     resetQueryRepresentation(qr, false);
     687              : 
     688          269 :     ext->p = INT_MAX;
     689          269 :     ext->q = 0;
     690          269 :     ptr = doc + ext->pos;
     691              : 
     692              :     /* find upper bound of cover from current position, move up */
     693          503 :     while (ptr - doc < len)
     694              :     {
     695          379 :         fillQueryRepresentationData(qr, ptr);
     696              : 
     697          379 :         if (TS_execute(GETQUERY(qr->query), qr,
     698              :                        TS_EXEC_EMPTY, checkcondition_QueryOperand))
     699              :         {
     700          145 :             if (WEP_GETPOS(ptr->pos) > ext->q)
     701              :             {
     702          145 :                 ext->q = WEP_GETPOS(ptr->pos);
     703          145 :                 ext->end = ptr;
     704          145 :                 lastpos = ptr - doc;
     705          145 :                 found = true;
     706              :             }
     707          145 :             break;
     708              :         }
     709          234 :         ptr++;
     710              :     }
     711              : 
     712          269 :     if (!found)
     713          124 :         return false;
     714              : 
     715          145 :     resetQueryRepresentation(qr, true);
     716              : 
     717          145 :     ptr = doc + lastpos;
     718              : 
     719              :     /* find lower bound of cover from found upper bound, move down */
     720          220 :     while (ptr >= doc + ext->pos)
     721              :     {
     722              :         /*
     723              :          * we scan doc from right to left, so pos info in reverse order!
     724              :          */
     725          220 :         fillQueryRepresentationData(qr, ptr);
     726              : 
     727          220 :         if (TS_execute(GETQUERY(qr->query), qr,
     728              :                        TS_EXEC_EMPTY, checkcondition_QueryOperand))
     729              :         {
     730          145 :             if (WEP_GETPOS(ptr->pos) < ext->p)
     731              :             {
     732          145 :                 ext->begin = ptr;
     733          145 :                 ext->p = WEP_GETPOS(ptr->pos);
     734              :             }
     735          145 :             break;
     736              :         }
     737           75 :         ptr--;
     738              :     }
     739              : 
     740          145 :     if (ext->p <= ext->q)
     741              :     {
     742              :         /*
     743              :          * set position for next try to next lexeme after beginning of found
     744              :          * cover
     745              :          */
     746          145 :         ext->pos = (ptr - doc) + 1;
     747          145 :         return true;
     748              :     }
     749              : 
     750            0 :     ext->pos++;
     751            0 :     return Cover(doc, len, qr, ext);
     752              : }
     753              : 
     754              : static DocRepresentation *
     755          128 : get_docrep(TSVector txt, QueryRepresentation *qr, int *doclen)
     756              : {
     757          128 :     QueryItem  *item = GETQUERY(qr->query);
     758              :     WordEntry  *entry,
     759              :                *firstentry;
     760              :     WordEntryPos *post;
     761              :     int32       dimt,           /* number of 'post' items */
     762              :                 j,
     763              :                 i,
     764              :                 nitem;
     765          128 :     int         len = qr->query->size * 4,
     766          128 :                 cur = 0;
     767              :     DocRepresentation *doc;
     768              : 
     769          128 :     doc = palloc_array(DocRepresentation, len);
     770              : 
     771              :     /*
     772              :      * Iterate through query to make DocRepresentation for words and it's
     773              :      * entries satisfied by query
     774              :      */
     775          524 :     for (i = 0; i < qr->query->size; i++)
     776              :     {
     777              :         QueryOperand *curoperand;
     778              : 
     779          396 :         if (item[i].type != QI_VAL)
     780          134 :             continue;
     781              : 
     782          262 :         curoperand = &item[i].qoperand;
     783              : 
     784          262 :         firstentry = entry = find_wordentry(txt, qr->query, curoperand, &nitem);
     785          262 :         if (!entry)
     786            5 :             continue;
     787              : 
     788              :         /* iterations over entries in tsvector */
     789          539 :         while (entry - firstentry < nitem)
     790              :         {
     791          282 :             if (entry->haspos)
     792              :             {
     793          274 :                 dimt = POSDATALEN(txt, entry);
     794          274 :                 post = POSDATAPTR(txt, entry);
     795              :             }
     796              :             else
     797              :             {
     798              :                 /* ignore words without positions */
     799            8 :                 entry++;
     800            8 :                 continue;
     801              :             }
     802              : 
     803          274 :             while (cur + dimt >= len)
     804              :             {
     805            0 :                 len *= 2;
     806            0 :                 doc = (DocRepresentation *) repalloc(doc, sizeof(DocRepresentation) * len);
     807              :             }
     808              : 
     809              :             /* iterations over entry's positions */
     810          593 :             for (j = 0; j < dimt; j++)
     811              :             {
     812          319 :                 if (curoperand->weight == 0 ||
     813           25 :                     curoperand->weight & (1 << WEP_GETWEIGHT(post[j])))
     814              :                 {
     815          309 :                     doc[cur].pos = post[j];
     816          309 :                     doc[cur].data.map.entry = entry;
     817          309 :                     doc[cur].data.map.item = (QueryItem *) curoperand;
     818          309 :                     cur++;
     819              :                 }
     820              :             }
     821              : 
     822          274 :             entry++;
     823              :         }
     824              :     }
     825              : 
     826          128 :     if (cur > 0)
     827              :     {
     828          124 :         DocRepresentation *rptr = doc + 1,
     829          124 :                    *wptr = doc,
     830              :                     storage;
     831              : 
     832              :         /*
     833              :          * Sort representation in ascending order by pos and entry
     834              :          */
     835          124 :         qsort(doc, cur, sizeof(DocRepresentation), compareDocR);
     836              : 
     837              :         /*
     838              :          * Join QueryItem per WordEntry and its position
     839              :          */
     840          124 :         storage.pos = doc->pos;
     841          124 :         storage.data.query.items = palloc_array(QueryItem *, qr->query->size);
     842          124 :         storage.data.query.items[0] = doc->data.map.item;
     843          124 :         storage.data.query.nitem = 1;
     844              : 
     845          309 :         while (rptr - doc < cur)
     846              :         {
     847          185 :             if (rptr->pos == (rptr - 1)->pos &&
     848            5 :                 rptr->data.map.entry == (rptr - 1)->data.map.entry)
     849              :             {
     850            5 :                 storage.data.query.items[storage.data.query.nitem] = rptr->data.map.item;
     851            5 :                 storage.data.query.nitem++;
     852              :             }
     853              :             else
     854              :             {
     855          180 :                 *wptr = storage;
     856          180 :                 wptr++;
     857          180 :                 storage.pos = rptr->pos;
     858          180 :                 storage.data.query.items = palloc_array(QueryItem *, qr->query->size);
     859          180 :                 storage.data.query.items[0] = rptr->data.map.item;
     860          180 :                 storage.data.query.nitem = 1;
     861              :             }
     862              : 
     863          185 :             rptr++;
     864              :         }
     865              : 
     866          124 :         *wptr = storage;
     867          124 :         wptr++;
     868              : 
     869          124 :         *doclen = wptr - doc;
     870          124 :         return doc;
     871              :     }
     872              : 
     873            4 :     pfree(doc);
     874            4 :     return NULL;
     875              : }
     876              : 
     877              : static float4
     878          128 : calc_rank_cd(const float4 *arrdata, TSVector txt, TSQuery query, int method)
     879              : {
     880              :     DocRepresentation *doc;
     881              :     int         len,
     882              :                 i,
     883          128 :                 doclen = 0;
     884              :     CoverExt    ext;
     885          128 :     double      Wdoc = 0.0;
     886              :     double      invws[NUM_WEIGHTS];
     887          128 :     double      SumDist = 0.0,
     888          128 :                 PrevExtPos = 0.0;
     889          128 :     int         NExtent = 0;
     890              :     QueryRepresentation qr;
     891              : 
     892              : 
     893          640 :     for (i = 0; i < NUM_WEIGHTS; i++)
     894              :     {
     895          512 :         invws[i] = ((double) ((arrdata[i] >= 0) ? arrdata[i] : default_weights[i]));
     896          512 :         if (invws[i] > 1.0)
     897            0 :             ereport(ERROR,
     898              :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     899              :                      errmsg("weight out of range")));
     900          512 :         invws[i] = 1.0 / invws[i];
     901              :     }
     902              : 
     903          128 :     qr.query = query;
     904          128 :     qr.operandData = palloc0_array(QueryRepresentationOperand, query->size);
     905              : 
     906          128 :     doc = get_docrep(txt, &qr, &doclen);
     907          128 :     if (!doc)
     908              :     {
     909            4 :         pfree(qr.operandData);
     910            4 :         return 0.0;
     911              :     }
     912              : 
     913          620 :     MemSet(&ext, 0, sizeof(CoverExt));
     914          269 :     while (Cover(doc, doclen, &qr, &ext))
     915              :     {
     916          145 :         double      Cpos = 0.0;
     917          145 :         double      InvSum = 0.0;
     918              :         double      CurExtPos;
     919              :         int         nNoise;
     920          145 :         DocRepresentation *ptr = ext.begin;
     921              : 
     922          365 :         while (ptr <= ext.end)
     923              :         {
     924          220 :             InvSum += invws[WEP_GETWEIGHT(ptr->pos)];
     925          220 :             ptr++;
     926              :         }
     927              : 
     928          145 :         Cpos = ((double) (ext.end - ext.begin + 1)) / InvSum;
     929              : 
     930              :         /*
     931              :          * if doc are big enough then ext.q may be equal to ext.p due to limit
     932              :          * of positional information. In this case we approximate number of
     933              :          * noise word as half cover's length
     934              :          */
     935          145 :         nNoise = (ext.q - ext.p) - (ext.end - ext.begin);
     936          145 :         if (nNoise < 0)
     937            0 :             nNoise = (ext.end - ext.begin) / 2;
     938          145 :         Wdoc += Cpos / ((double) (1 + nNoise));
     939              : 
     940          145 :         CurExtPos = ((double) (ext.q + ext.p)) / 2.0;
     941          145 :         if (NExtent > 0 && CurExtPos > PrevExtPos /* prevent division by
     942              :                                                      * zero in a case of
     943              :               * multiple lexize */ )
     944           35 :             SumDist += 1.0 / (CurExtPos - PrevExtPos);
     945              : 
     946          145 :         PrevExtPos = CurExtPos;
     947          145 :         NExtent++;
     948              :     }
     949              : 
     950          124 :     if ((method & RANK_NORM_LOGLENGTH) && txt->size > 0)
     951            0 :         Wdoc /= log((double) (cnt_length(txt) + 1));
     952              : 
     953          124 :     if (method & RANK_NORM_LENGTH)
     954              :     {
     955            0 :         len = cnt_length(txt);
     956            0 :         if (len > 0)
     957            0 :             Wdoc /= (double) len;
     958              :     }
     959              : 
     960          124 :     if ((method & RANK_NORM_EXTDIST) && NExtent > 0 && SumDist > 0)
     961            0 :         Wdoc /= ((double) NExtent) / SumDist;
     962              : 
     963          124 :     if ((method & RANK_NORM_UNIQ) && txt->size > 0)
     964            0 :         Wdoc /= (double) (txt->size);
     965              : 
     966          124 :     if ((method & RANK_NORM_LOGUNIQ) && txt->size > 0)
     967            0 :         Wdoc /= log((double) (txt->size + 1)) / log(2.0);
     968              : 
     969          124 :     if (method & RANK_NORM_RDIVRPLUS1)
     970            0 :         Wdoc /= (Wdoc + 1);
     971              : 
     972          124 :     pfree(doc);
     973              : 
     974          124 :     pfree(qr.operandData);
     975              : 
     976          124 :     return (float4) Wdoc;
     977              : }
     978              : 
     979              : Datum
     980            0 : ts_rankcd_wttf(PG_FUNCTION_ARGS)
     981              : {
     982            0 :     ArrayType  *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
     983            0 :     TSVector    txt = PG_GETARG_TSVECTOR(1);
     984            0 :     TSQuery     query = PG_GETARG_TSQUERY(2);
     985            0 :     int         method = PG_GETARG_INT32(3);
     986              :     float       weights[NUM_WEIGHTS];
     987              :     float       res;
     988              : 
     989            0 :     getWeights(win, weights);
     990            0 :     res = calc_rank_cd(weights, txt, query, method);
     991              : 
     992            0 :     PG_FREE_IF_COPY(win, 0);
     993            0 :     PG_FREE_IF_COPY(txt, 1);
     994            0 :     PG_FREE_IF_COPY(query, 2);
     995            0 :     PG_RETURN_FLOAT4(res);
     996              : }
     997              : 
     998              : Datum
     999            0 : ts_rankcd_wtt(PG_FUNCTION_ARGS)
    1000              : {
    1001            0 :     ArrayType  *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    1002            0 :     TSVector    txt = PG_GETARG_TSVECTOR(1);
    1003            0 :     TSQuery     query = PG_GETARG_TSQUERY(2);
    1004              :     float       weights[NUM_WEIGHTS];
    1005              :     float       res;
    1006              : 
    1007            0 :     getWeights(win, weights);
    1008            0 :     res = calc_rank_cd(weights, txt, query, DEF_NORM_METHOD);
    1009              : 
    1010            0 :     PG_FREE_IF_COPY(win, 0);
    1011            0 :     PG_FREE_IF_COPY(txt, 1);
    1012            0 :     PG_FREE_IF_COPY(query, 2);
    1013            0 :     PG_RETURN_FLOAT4(res);
    1014              : }
    1015              : 
    1016              : Datum
    1017            0 : ts_rankcd_ttf(PG_FUNCTION_ARGS)
    1018              : {
    1019            0 :     TSVector    txt = PG_GETARG_TSVECTOR(0);
    1020            0 :     TSQuery     query = PG_GETARG_TSQUERY(1);
    1021            0 :     int         method = PG_GETARG_INT32(2);
    1022              :     float       res;
    1023              : 
    1024            0 :     res = calc_rank_cd(default_weights, txt, query, method);
    1025              : 
    1026            0 :     PG_FREE_IF_COPY(txt, 0);
    1027            0 :     PG_FREE_IF_COPY(query, 1);
    1028            0 :     PG_RETURN_FLOAT4(res);
    1029              : }
    1030              : 
    1031              : Datum
    1032          128 : ts_rankcd_tt(PG_FUNCTION_ARGS)
    1033              : {
    1034          128 :     TSVector    txt = PG_GETARG_TSVECTOR(0);
    1035          128 :     TSQuery     query = PG_GETARG_TSQUERY(1);
    1036              :     float       res;
    1037              : 
    1038          128 :     res = calc_rank_cd(default_weights, txt, query, DEF_NORM_METHOD);
    1039              : 
    1040          128 :     PG_FREE_IF_COPY(txt, 0);
    1041          128 :     PG_FREE_IF_COPY(query, 1);
    1042          128 :     PG_RETURN_FLOAT4(res);
    1043              : }
        

Generated by: LCOV version 2.0-1