Coverage Report

Created: 2026-11-09 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/utils/adt/tsrank.c
Line
Count
Source
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
0
#define NUM_WEIGHTS 4
25
static const float default_weights[NUM_WEIGHTS] = {0.1f, 0.2f, 0.4f, 1.0f};
26
27
0
#define wpos(wep) ( w[ WEP_GETWEIGHT(wep) ] )
28
29
0
#define RANK_NO_NORM      0x00
30
0
#define RANK_NORM_LOGLENGTH   0x01
31
0
#define RANK_NORM_LENGTH    0x02
32
0
#define RANK_NORM_EXTDIST   0x04
33
0
#define RANK_NORM_UNIQ      0x08
34
0
#define RANK_NORM_LOGUNIQ   0x10
35
0
#define RANK_NORM_RDIVRPLUS1  0x20
36
0
#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
word_distance(int32 w)
46
0
{
47
0
  if (w > 100)
48
0
    return 1e-30f;
49
50
0
  return 1.0 / (1.005 + 0.05 * exp(((float4) w) / 1.5 - 2));
51
0
}
52
53
static int
54
cnt_length(TSVector t)
55
0
{
56
0
  WordEntry  *ptr = ARRPTR(t),
57
0
         *end = (WordEntry *) STRPTR(t);
58
0
  int     len = 0;
59
60
0
  while (ptr < end)
61
0
  {
62
0
    int     clen = POSDATALEN(t, ptr);
63
64
0
    if (clen == 0)
65
0
      len += 1;
66
0
    else
67
0
      len += clen;
68
69
0
    ptr++;
70
0
  }
71
72
0
  return len;
73
0
}
74
75
76
#define WordECompareQueryItem(e,q,p,i,m) \
77
0
  tsCompareString((q) + (i)->distance, (i)->length, \
78
0
          (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
find_wordentry(TSVector t, TSQuery q, QueryOperand *item, int32 *nitem)
88
0
{
89
0
  WordEntry  *StopLow = ARRPTR(t);
90
0
  WordEntry  *StopHigh = (WordEntry *) STRPTR(t);
91
0
  WordEntry  *StopMiddle = StopHigh;
92
0
  int     difference;
93
94
0
  *nitem = 0;
95
96
  /* Loop invariant: StopLow <= item < StopHigh */
97
0
  while (StopLow < StopHigh)
98
0
  {
99
0
    StopMiddle = StopLow + (StopHigh - StopLow) / 2;
100
0
    difference = WordECompareQueryItem(STRPTR(t), GETOPERAND(q), StopMiddle, item, false);
101
0
    if (difference == 0)
102
0
    {
103
0
      StopHigh = StopMiddle;
104
0
      *nitem = 1;
105
0
      break;
106
0
    }
107
0
    else if (difference > 0)
108
0
      StopLow = StopMiddle + 1;
109
0
    else
110
0
      StopHigh = StopMiddle;
111
0
  }
112
113
0
  if (item->prefix)
114
0
  {
115
0
    if (StopLow >= StopHigh)
116
0
      StopMiddle = StopHigh;
117
118
0
    *nitem = 0;
119
120
0
    while (StopMiddle < (WordEntry *) STRPTR(t) &&
121
0
         WordECompareQueryItem(STRPTR(t), GETOPERAND(q), StopMiddle, item, true) == 0)
122
0
    {
123
0
      (*nitem)++;
124
0
      StopMiddle++;
125
0
    }
126
0
  }
127
128
0
  return (*nitem > 0) ? StopHigh : NULL;
129
0
}
130
131
132
/*
133
 * sort QueryOperands by (length, word)
134
 */
135
static int
136
compareQueryOperand(const void *a, const void *b, void *arg)
137
0
{
138
0
  char     *operand = (char *) arg;
139
0
  QueryOperand *qa = (*(QueryOperand *const *) a);
140
0
  QueryOperand *qb = (*(QueryOperand *const *) b);
141
142
0
  return tsCompareString(operand + qa->distance, qa->length,
143
0
               operand + qb->distance, qb->length,
144
0
               false);
145
0
}
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
SortAndUniqItems(TSQuery q, int *size)
156
0
{
157
0
  char     *operand = GETOPERAND(q);
158
0
  QueryItem  *item = GETQUERY(q);
159
0
  QueryOperand **res,
160
0
        **ptr,
161
0
        **prevptr;
162
163
0
  ptr = res = palloc_array(QueryOperand *, *size);
164
165
  /* Collect all operands from the tree to res */
166
0
  while ((*size)--)
167
0
  {
168
0
    if (item->type == QI_VAL)
169
0
    {
170
0
      *ptr = (QueryOperand *) item;
171
0
      ptr++;
172
0
    }
173
0
    item++;
174
0
  }
175
176
0
  *size = ptr - res;
177
0
  if (*size < 2)
178
0
    return res;
179
180
0
  qsort_arg(res, *size, sizeof(QueryOperand *), compareQueryOperand, operand);
181
182
0
  ptr = res + 1;
183
0
  prevptr = res;
184
185
  /* remove duplicates */
186
0
  while (ptr - res < *size)
187
0
  {
188
0
    if (compareQueryOperand(ptr, prevptr, operand) != 0)
189
0
    {
190
0
      prevptr++;
191
0
      *prevptr = *ptr;
192
0
    }
193
0
    ptr++;
194
0
  }
195
196
0
  *size = prevptr + 1 - res;
197
0
  return res;
198
0
}
199
200
static float
201
calc_rank_and(const float *w, TSVector t, TSQuery q)
202
0
{
203
0
  WordEntryPosVector **pos;
204
0
  WordEntryPosVector1 posnull;
205
0
  WordEntryPosVector *POSNULL;
206
0
  int     i,
207
0
        k,
208
0
        l,
209
0
        p;
210
0
  WordEntry  *entry,
211
0
         *firstentry;
212
0
  WordEntryPos *post,
213
0
         *ct;
214
0
  int32   dimt,
215
0
        lenct,
216
0
        dist,
217
0
        nitem;
218
0
  float   res = -1.0;
219
0
  QueryOperand **item;
220
0
  int     size = q->size;
221
222
0
  item = SortAndUniqItems(q, &size);
223
0
  if (size < 2)
224
0
  {
225
0
    pfree(item);
226
0
    return calc_rank_or(w, t, q);
227
0
  }
228
0
  pos = palloc0_array(WordEntryPosVector *, q->size);
229
230
  /* A dummy WordEntryPos array to use when haspos is false */
231
0
  posnull.npos = 1;
232
0
  posnull.pos[0] = 0;
233
0
  WEP_SETPOS(posnull.pos[0], MAXENTRYPOS - 1);
234
0
  POSNULL = (WordEntryPosVector *) &posnull;
235
236
0
  for (i = 0; i < size; i++)
237
0
  {
238
0
    firstentry = entry = find_wordentry(t, q, item[i], &nitem);
239
0
    if (!entry)
240
0
      continue;
241
242
0
    while (entry - firstentry < nitem)
243
0
    {
244
0
      if (entry->haspos)
245
0
        pos[i] = _POSVECPTR(t, entry);
246
0
      else
247
0
        pos[i] = POSNULL;
248
249
0
      dimt = pos[i]->npos;
250
0
      post = pos[i]->pos;
251
0
      for (k = 0; k < i; k++)
252
0
      {
253
0
        if (!pos[k])
254
0
          continue;
255
0
        lenct = pos[k]->npos;
256
0
        ct = pos[k]->pos;
257
0
        for (l = 0; l < dimt; l++)
258
0
        {
259
0
          for (p = 0; p < lenct; p++)
260
0
          {
261
0
            dist = abs((int) WEP_GETPOS(post[l]) - (int) WEP_GETPOS(ct[p]));
262
0
            if (dist || (dist == 0 && (pos[i] == POSNULL || pos[k] == POSNULL)))
263
0
            {
264
0
              float   curw;
265
266
0
              if (!dist)
267
0
                dist = MAXENTRYPOS;
268
0
              curw = sqrt(wpos(post[l]) * wpos(ct[p]) * word_distance(dist));
269
0
              res = (res < 0) ? curw : 1.0 - (1.0 - res) * (1.0 - curw);
270
0
            }
271
0
          }
272
0
        }
273
0
      }
274
275
0
      entry++;
276
0
    }
277
0
  }
278
0
  pfree(pos);
279
0
  pfree(item);
280
0
  return res;
281
0
}
282
283
static float
284
calc_rank_or(const float *w, TSVector t, TSQuery q)
285
0
{
286
0
  WordEntry  *entry,
287
0
         *firstentry;
288
0
  WordEntryPosVector1 posnull;
289
0
  WordEntryPos *post;
290
0
  int32   dimt,
291
0
        j,
292
0
        i,
293
0
        nitem;
294
0
  float   res = 0.0;
295
0
  QueryOperand **item;
296
0
  int     size = q->size;
297
298
  /* A dummy WordEntryPos array to use when haspos is false */
299
0
  posnull.npos = 1;
300
0
  posnull.pos[0] = 0;
301
302
0
  item = SortAndUniqItems(q, &size);
303
304
0
  for (i = 0; i < size; i++)
305
0
  {
306
0
    float   resj,
307
0
          wjm;
308
0
    int32   jm;
309
310
0
    firstentry = entry = find_wordentry(t, q, item[i], &nitem);
311
0
    if (!entry)
312
0
      continue;
313
314
0
    while (entry - firstentry < nitem)
315
0
    {
316
      /* Identify the weight data to use */
317
0
      if (entry->haspos)
318
0
      {
319
0
        dimt = POSDATALEN(t, entry);
320
0
        post = POSDATAPTR(t, entry);
321
0
      }
322
0
      else
323
0
      {
324
0
        dimt = posnull.npos;
325
0
        post = posnull.pos;
326
0
      }
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
0
      resj = 0.0;
356
0
      wjm = -1.0;
357
0
      jm = 0;
358
0
      for (j = 0; j < dimt; j++)
359
0
      {
360
0
        resj = resj + wpos(post[j]) / ((j + 1) * (j + 1));
361
0
        if (wpos(post[j]) > wjm)
362
0
        {
363
0
          wjm = wpos(post[j]);
364
0
          jm = j;
365
0
        }
366
0
      }
367
368
      /* apply correction as explained above, then add to res */
369
0
      res = res + (resj - wjm / ((jm + 1) * (jm + 1)) + wjm) / 1.64493406685;
370
371
0
      entry++;
372
0
    }
373
0
  }
374
0
  if (size > 0)
375
0
    res = res / size;
376
0
  pfree(item);
377
0
  return res;
378
0
}
379
380
static float
381
calc_rank(const float *w, TSVector t, TSQuery q, int32 method)
382
0
{
383
0
  QueryItem  *item = GETQUERY(q);
384
0
  float   res = 0.0;
385
0
  int     len;
386
387
0
  if (!t->size || !q->size)
388
0
    return 0.0;
389
390
  /* XXX: What about NOT? */
391
0
  res = (item->type == QI_OPR && (item->qoperator.oper == OP_AND ||
392
0
                  item->qoperator.oper == OP_PHRASE)) ?
393
0
    calc_rank_and(w, t, q) :
394
0
    calc_rank_or(w, t, q);
395
396
0
  if (res < 0)
397
0
    res = 1e-20f;
398
399
0
  if ((method & RANK_NORM_LOGLENGTH) && t->size > 0)
400
0
    res /= log((double) (cnt_length(t) + 1)) / log(2.0);
401
402
0
  if (method & RANK_NORM_LENGTH)
403
0
  {
404
0
    len = cnt_length(t);
405
0
    if (len > 0)
406
0
      res /= (float) len;
407
0
  }
408
409
  /* RANK_NORM_EXTDIST not applicable */
410
411
0
  if ((method & RANK_NORM_UNIQ) && t->size > 0)
412
0
    res /= (float) (t->size);
413
414
0
  if ((method & RANK_NORM_LOGUNIQ) && t->size > 0)
415
0
    res /= log((double) (t->size + 1)) / log(2.0);
416
417
0
  if (method & RANK_NORM_RDIVRPLUS1)
418
0
    res /= (res + 1);
419
420
0
  return res;
421
0
}
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
getWeights(ArrayType *win, float *ws)
429
0
{
430
0
  int     i;
431
0
  float4     *arrdata;
432
433
0
  Assert(win != NULL);
434
435
0
  if (ARR_NDIM(win) != 1)
436
0
    ereport(ERROR,
437
0
        (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
438
0
         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
0
        (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
443
0
         errmsg("array of weight is too short")));
444
445
0
  if (array_contains_nulls(win))
446
0
    ereport(ERROR,
447
0
        (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
448
0
         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
0
  {
453
0
    ws[i] = (arrdata[i] >= 0) ? arrdata[i] : default_weights[i];
454
0
    if (ws[i] > 1.0)
455
0
      ereport(ERROR,
456
0
          (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
457
0
           errmsg("weight out of range")));
458
0
  }
459
0
}
460
461
Datum
462
ts_rank_wttf(PG_FUNCTION_ARGS)
463
0
{
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
0
  float   weights[NUM_WEIGHTS];
469
0
  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
0
}
479
480
Datum
481
ts_rank_wtt(PG_FUNCTION_ARGS)
482
0
{
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
0
  float   weights[NUM_WEIGHTS];
487
0
  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
0
}
497
498
Datum
499
ts_rank_ttf(PG_FUNCTION_ARGS)
500
0
{
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
0
  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
0
}
512
513
Datum
514
ts_rank_tt(PG_FUNCTION_ARGS)
515
0
{
516
0
  TSVector  txt = PG_GETARG_TSVECTOR(0);
517
0
  TSQuery   query = PG_GETARG_TSQUERY(1);
518
0
  float   res;
519
520
0
  res = calc_rank(default_weights, txt, query, DEF_NORM_METHOD);
521
522
0
  PG_FREE_IF_COPY(txt, 0);
523
0
  PG_FREE_IF_COPY(query, 1);
524
0
  PG_RETURN_FLOAT4(res);
525
0
}
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
compareDocR(const void *va, const void *vb)
548
0
{
549
0
  const DocRepresentation *a = (const DocRepresentation *) va;
550
0
  const DocRepresentation *b = (const DocRepresentation *) vb;
551
552
0
  if (WEP_GETPOS(a->pos) == WEP_GETPOS(b->pos))
553
0
  {
554
0
    if (WEP_GETWEIGHT(a->pos) == WEP_GETWEIGHT(b->pos))
555
0
    {
556
0
      if (a->data.map.entry == b->data.map.entry)
557
0
        return 0;
558
559
0
      return (a->data.map.entry > b->data.map.entry) ? 1 : -1;
560
0
    }
561
562
0
    return (WEP_GETWEIGHT(a->pos) > WEP_GETWEIGHT(b->pos)) ? 1 : -1;
563
0
  }
564
565
0
  return (WEP_GETPOS(a->pos) > WEP_GETPOS(b->pos)) ? 1 : -1;
566
0
}
567
568
0
#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
0
  ( (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
checkcondition_QueryOperand(void *checkval, QueryOperand *val,
592
              ExecPhraseData *data)
593
0
{
594
0
  QueryRepresentation *qr = (QueryRepresentation *) checkval;
595
0
  QueryRepresentationOperand *opData = QR_GET_OPERAND_DATA(qr, val);
596
597
0
  if (!opData->operandexists)
598
0
    return TS_NO;
599
600
0
  if (data)
601
0
  {
602
0
    data->npos = opData->npos;
603
0
    data->pos = opData->pos;
604
0
    if (opData->reverseinsert)
605
0
      data->pos += MAXQROPOS - opData->npos;
606
0
  }
607
608
0
  return TS_YES;
609
0
}
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
resetQueryRepresentation(QueryRepresentation *qr, bool reverseinsert)
622
0
{
623
0
  int     i;
624
625
0
  for (i = 0; i < qr->query->size; i++)
626
0
  {
627
0
    qr->operandData[i].operandexists = false;
628
0
    qr->operandData[i].reverseinsert = reverseinsert;
629
0
    qr->operandData[i].npos = 0;
630
0
  }
631
0
}
632
633
static void
634
fillQueryRepresentationData(QueryRepresentation *qr, DocRepresentation *entry)
635
0
{
636
0
  int     i;
637
0
  int     lastPos;
638
0
  QueryRepresentationOperand *opData;
639
640
0
  for (i = 0; i < entry->data.query.nitem; i++)
641
0
  {
642
0
    if (entry->data.query.items[i]->type != QI_VAL)
643
0
      continue;
644
645
0
    opData = QR_GET_OPERAND_DATA(qr, entry->data.query.items[i]);
646
647
0
    opData->operandexists = true;
648
649
0
    if (opData->npos == 0)
650
0
    {
651
0
      lastPos = (opData->reverseinsert) ? (MAXQROPOS - 1) : 0;
652
0
      opData->pos[lastPos] = entry->pos;
653
0
      opData->npos++;
654
0
      continue;
655
0
    }
656
657
0
    lastPos = opData->reverseinsert ?
658
0
      (MAXQROPOS - opData->npos) :
659
0
      (opData->npos - 1);
660
661
0
    if (WEP_GETPOS(opData->pos[lastPos]) != WEP_GETPOS(entry->pos))
662
0
    {
663
0
      lastPos = opData->reverseinsert ?
664
0
        (MAXQROPOS - 1 - opData->npos) :
665
0
        (opData->npos);
666
667
0
      opData->pos[lastPos] = entry->pos;
668
0
      opData->npos++;
669
0
    }
670
0
  }
671
0
}
672
673
static bool
674
Cover(DocRepresentation *doc, int len, QueryRepresentation *qr, CoverExt *ext)
675
0
{
676
0
  DocRepresentation *ptr;
677
0
  int     lastpos = ext->pos;
678
0
  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
0
  check_stack_depth();
685
686
0
  resetQueryRepresentation(qr, false);
687
688
0
  ext->p = INT_MAX;
689
0
  ext->q = 0;
690
0
  ptr = doc + ext->pos;
691
692
  /* find upper bound of cover from current position, move up */
693
0
  while (ptr - doc < len)
694
0
  {
695
0
    fillQueryRepresentationData(qr, ptr);
696
697
0
    if (TS_execute(GETQUERY(qr->query), qr,
698
0
             TS_EXEC_EMPTY, checkcondition_QueryOperand))
699
0
    {
700
0
      if (WEP_GETPOS(ptr->pos) > ext->q)
701
0
      {
702
0
        ext->q = WEP_GETPOS(ptr->pos);
703
0
        ext->end = ptr;
704
0
        lastpos = ptr - doc;
705
0
        found = true;
706
0
      }
707
0
      break;
708
0
    }
709
0
    ptr++;
710
0
  }
711
712
0
  if (!found)
713
0
    return false;
714
715
0
  resetQueryRepresentation(qr, true);
716
717
0
  ptr = doc + lastpos;
718
719
  /* find lower bound of cover from found upper bound, move down */
720
0
  while (ptr >= doc + ext->pos)
721
0
  {
722
    /*
723
     * we scan doc from right to left, so pos info in reverse order!
724
     */
725
0
    fillQueryRepresentationData(qr, ptr);
726
727
0
    if (TS_execute(GETQUERY(qr->query), qr,
728
0
             TS_EXEC_EMPTY, checkcondition_QueryOperand))
729
0
    {
730
0
      if (WEP_GETPOS(ptr->pos) < ext->p)
731
0
      {
732
0
        ext->begin = ptr;
733
0
        ext->p = WEP_GETPOS(ptr->pos);
734
0
      }
735
0
      break;
736
0
    }
737
0
    ptr--;
738
0
  }
739
740
0
  if (ext->p <= ext->q)
741
0
  {
742
    /*
743
     * set position for next try to next lexeme after beginning of found
744
     * cover
745
     */
746
0
    ext->pos = (ptr - doc) + 1;
747
0
    return true;
748
0
  }
749
750
0
  ext->pos++;
751
0
  return Cover(doc, len, qr, ext);
752
0
}
753
754
static DocRepresentation *
755
get_docrep(TSVector txt, QueryRepresentation *qr, int *doclen)
756
0
{
757
0
  QueryItem  *item = GETQUERY(qr->query);
758
0
  WordEntry  *entry,
759
0
         *firstentry;
760
0
  WordEntryPos *post;
761
0
  int32   dimt,     /* number of 'post' items */
762
0
        j,
763
0
        i,
764
0
        nitem;
765
0
  int     len = qr->query->size * 4,
766
0
        cur = 0;
767
0
  DocRepresentation *doc;
768
769
0
  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
0
  for (i = 0; i < qr->query->size; i++)
776
0
  {
777
0
    QueryOperand *curoperand;
778
779
0
    if (item[i].type != QI_VAL)
780
0
      continue;
781
782
0
    curoperand = &item[i].qoperand;
783
784
0
    firstentry = entry = find_wordentry(txt, qr->query, curoperand, &nitem);
785
0
    if (!entry)
786
0
      continue;
787
788
    /* iterations over entries in tsvector */
789
0
    while (entry - firstentry < nitem)
790
0
    {
791
0
      if (entry->haspos)
792
0
      {
793
0
        dimt = POSDATALEN(txt, entry);
794
0
        post = POSDATAPTR(txt, entry);
795
0
      }
796
0
      else
797
0
      {
798
        /* ignore words without positions */
799
0
        entry++;
800
0
        continue;
801
0
      }
802
803
0
      while (cur + dimt >= len)
804
0
      {
805
0
        len *= 2;
806
0
        doc = repalloc_array(doc, DocRepresentation, len);
807
0
      }
808
809
      /* iterations over entry's positions */
810
0
      for (j = 0; j < dimt; j++)
811
0
      {
812
0
        if (curoperand->weight == 0 ||
813
0
          curoperand->weight & (1 << WEP_GETWEIGHT(post[j])))
814
0
        {
815
0
          doc[cur].pos = post[j];
816
0
          doc[cur].data.map.entry = entry;
817
0
          doc[cur].data.map.item = (QueryItem *) curoperand;
818
0
          cur++;
819
0
        }
820
0
      }
821
822
0
      entry++;
823
0
    }
824
0
  }
825
826
0
  if (cur > 0)
827
0
  {
828
0
    DocRepresentation *rptr = doc + 1,
829
0
           *wptr = doc,
830
0
          storage;
831
832
    /*
833
     * Sort representation in ascending order by pos and entry
834
     */
835
0
    qsort(doc, cur, sizeof(DocRepresentation), compareDocR);
836
837
    /*
838
     * Join QueryItem per WordEntry and its position
839
     */
840
0
    storage.pos = doc->pos;
841
0
    storage.data.query.items = palloc_array(QueryItem *, qr->query->size);
842
0
    storage.data.query.items[0] = doc->data.map.item;
843
0
    storage.data.query.nitem = 1;
844
845
0
    while (rptr - doc < cur)
846
0
    {
847
0
      if (rptr->pos == (rptr - 1)->pos &&
848
0
        rptr->data.map.entry == (rptr - 1)->data.map.entry)
849
0
      {
850
0
        storage.data.query.items[storage.data.query.nitem] = rptr->data.map.item;
851
0
        storage.data.query.nitem++;
852
0
      }
853
0
      else
854
0
      {
855
0
        *wptr = storage;
856
0
        wptr++;
857
0
        storage.pos = rptr->pos;
858
0
        storage.data.query.items = palloc_array(QueryItem *, qr->query->size);
859
0
        storage.data.query.items[0] = rptr->data.map.item;
860
0
        storage.data.query.nitem = 1;
861
0
      }
862
863
0
      rptr++;
864
0
    }
865
866
0
    *wptr = storage;
867
0
    wptr++;
868
869
0
    *doclen = wptr - doc;
870
0
    return doc;
871
0
  }
872
873
0
  pfree(doc);
874
0
  return NULL;
875
0
}
876
877
static float4
878
calc_rank_cd(const float4 *arrdata, TSVector txt, TSQuery query, int method)
879
0
{
880
0
  DocRepresentation *doc;
881
0
  int     len,
882
0
        i,
883
0
        doclen = 0;
884
0
  CoverExt  ext;
885
0
  double    Wdoc = 0.0;
886
0
  double    invws[NUM_WEIGHTS];
887
0
  double    SumDist = 0.0,
888
0
        PrevExtPos = 0.0;
889
0
  int     NExtent = 0;
890
0
  QueryRepresentation qr;
891
892
893
0
  for (i = 0; i < NUM_WEIGHTS; i++)
894
0
  {
895
0
    invws[i] = ((double) ((arrdata[i] >= 0) ? arrdata[i] : default_weights[i]));
896
0
    if (invws[i] > 1.0)
897
0
      ereport(ERROR,
898
0
          (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
899
0
           errmsg("weight out of range")));
900
0
    invws[i] = 1.0 / invws[i];
901
0
  }
902
903
0
  qr.query = query;
904
0
  qr.operandData = palloc0_array(QueryRepresentationOperand, query->size);
905
906
0
  doc = get_docrep(txt, &qr, &doclen);
907
0
  if (!doc)
908
0
  {
909
0
    pfree(qr.operandData);
910
0
    return 0.0;
911
0
  }
912
913
0
  MemSet(&ext, 0, sizeof(CoverExt));
914
0
  while (Cover(doc, doclen, &qr, &ext))
915
0
  {
916
0
    double    Cpos = 0.0;
917
0
    double    InvSum = 0.0;
918
0
    double    CurExtPos;
919
0
    int     nNoise;
920
0
    DocRepresentation *ptr = ext.begin;
921
922
0
    while (ptr <= ext.end)
923
0
    {
924
0
      InvSum += invws[WEP_GETWEIGHT(ptr->pos)];
925
0
      ptr++;
926
0
    }
927
928
0
    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
0
    nNoise = (ext.q - ext.p) - (ext.end - ext.begin);
936
0
    if (nNoise < 0)
937
0
      nNoise = (ext.end - ext.begin) / 2;
938
0
    Wdoc += Cpos / ((double) (1 + nNoise));
939
940
0
    CurExtPos = ((double) (ext.q + ext.p)) / 2.0;
941
0
    if (NExtent > 0 && CurExtPos > PrevExtPos  /* prevent division by
942
                           * zero in a case of
943
0
        * multiple lexize */ )
944
0
      SumDist += 1.0 / (CurExtPos - PrevExtPos);
945
946
0
    PrevExtPos = CurExtPos;
947
0
    NExtent++;
948
0
  }
949
950
0
  if ((method & RANK_NORM_LOGLENGTH) && txt->size > 0)
951
0
    Wdoc /= log((double) (cnt_length(txt) + 1));
952
953
0
  if (method & RANK_NORM_LENGTH)
954
0
  {
955
0
    len = cnt_length(txt);
956
0
    if (len > 0)
957
0
      Wdoc /= (double) len;
958
0
  }
959
960
0
  if ((method & RANK_NORM_EXTDIST) && NExtent > 0 && SumDist > 0)
961
0
    Wdoc /= ((double) NExtent) / SumDist;
962
963
0
  if ((method & RANK_NORM_UNIQ) && txt->size > 0)
964
0
    Wdoc /= (double) (txt->size);
965
966
0
  if ((method & RANK_NORM_LOGUNIQ) && txt->size > 0)
967
0
    Wdoc /= log((double) (txt->size + 1)) / log(2.0);
968
969
0
  if (method & RANK_NORM_RDIVRPLUS1)
970
0
    Wdoc /= (Wdoc + 1);
971
972
0
  pfree(doc);
973
974
0
  pfree(qr.operandData);
975
976
0
  return (float4) Wdoc;
977
0
}
978
979
Datum
980
ts_rankcd_wttf(PG_FUNCTION_ARGS)
981
0
{
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
0
  float   weights[NUM_WEIGHTS];
987
0
  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
0
}
997
998
Datum
999
ts_rankcd_wtt(PG_FUNCTION_ARGS)
1000
0
{
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
0
  float   weights[NUM_WEIGHTS];
1005
0
  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
0
}
1015
1016
Datum
1017
ts_rankcd_ttf(PG_FUNCTION_ARGS)
1018
0
{
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
0
  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
0
}
1030
1031
Datum
1032
ts_rankcd_tt(PG_FUNCTION_ARGS)
1033
0
{
1034
0
  TSVector  txt = PG_GETARG_TSVECTOR(0);
1035
0
  TSQuery   query = PG_GETARG_TSQUERY(1);
1036
0
  float   res;
1037
1038
0
  res = calc_rank_cd(default_weights, txt, query, DEF_NORM_METHOD);
1039
1040
0
  PG_FREE_IF_COPY(txt, 0);
1041
0
  PG_FREE_IF_COPY(query, 1);
1042
0
  PG_RETURN_FLOAT4(res);
1043
0
}