summaryrefslogtreecommitdiffstats
path: root/src/backend/utils/adt/tsrank.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/backend/utils/adt/tsrank.c1012
1 files changed, 1012 insertions, 0 deletions
diff --git a/src/backend/utils/adt/tsrank.c b/src/backend/utils/adt/tsrank.c
new file mode 100644
index 0000000..977f700
--- /dev/null
+++ b/src/backend/utils/adt/tsrank.c
@@ -0,0 +1,1012 @@
+/*-------------------------------------------------------------------------
+ *
+ * tsrank.c
+ * rank tsvector by tsquery
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/tsrank.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <limits.h>
+#include <math.h>
+
+#include "miscadmin.h"
+#include "tsearch/ts_utils.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+
+static const float weights[] = {0.1f, 0.2f, 0.4f, 1.0f};
+
+#define wpos(wep) ( w[ WEP_GETWEIGHT(wep) ] )
+
+#define RANK_NO_NORM 0x00
+#define RANK_NORM_LOGLENGTH 0x01
+#define RANK_NORM_LENGTH 0x02
+#define RANK_NORM_EXTDIST 0x04
+#define RANK_NORM_UNIQ 0x08
+#define RANK_NORM_LOGUNIQ 0x10
+#define RANK_NORM_RDIVRPLUS1 0x20
+#define DEF_NORM_METHOD RANK_NO_NORM
+
+static float calc_rank_or(const float *w, TSVector t, TSQuery q);
+static float calc_rank_and(const float *w, TSVector t, TSQuery q);
+
+/*
+ * Returns a weight of a word collocation
+ */
+static float4
+word_distance(int32 w)
+{
+ if (w > 100)
+ return 1e-30f;
+
+ return 1.0 / (1.005 + 0.05 * exp(((float4) w) / 1.5 - 2));
+}
+
+static int
+cnt_length(TSVector t)
+{
+ WordEntry *ptr = ARRPTR(t),
+ *end = (WordEntry *) STRPTR(t);
+ int len = 0;
+
+ while (ptr < end)
+ {
+ int clen = POSDATALEN(t, ptr);
+
+ if (clen == 0)
+ len += 1;
+ else
+ len += clen;
+
+ ptr++;
+ }
+
+ return len;
+}
+
+
+#define WordECompareQueryItem(e,q,p,i,m) \
+ tsCompareString((q) + (i)->distance, (i)->length, \
+ (e) + (p)->pos, (p)->len, (m))
+
+
+/*
+ * Returns a pointer to a WordEntry's array corresponding to 'item' from
+ * tsvector 't'. 'q' is the TSQuery containing 'item'.
+ * Returns NULL if not found.
+ */
+static WordEntry *
+find_wordentry(TSVector t, TSQuery q, QueryOperand *item, int32 *nitem)
+{
+ WordEntry *StopLow = ARRPTR(t);
+ WordEntry *StopHigh = (WordEntry *) STRPTR(t);
+ WordEntry *StopMiddle = StopHigh;
+ int difference;
+
+ *nitem = 0;
+
+ /* Loop invariant: StopLow <= item < StopHigh */
+ while (StopLow < StopHigh)
+ {
+ StopMiddle = StopLow + (StopHigh - StopLow) / 2;
+ difference = WordECompareQueryItem(STRPTR(t), GETOPERAND(q), StopMiddle, item, false);
+ if (difference == 0)
+ {
+ StopHigh = StopMiddle;
+ *nitem = 1;
+ break;
+ }
+ else if (difference > 0)
+ StopLow = StopMiddle + 1;
+ else
+ StopHigh = StopMiddle;
+ }
+
+ if (item->prefix)
+ {
+ if (StopLow >= StopHigh)
+ StopMiddle = StopHigh;
+
+ *nitem = 0;
+
+ while (StopMiddle < (WordEntry *) STRPTR(t) &&
+ WordECompareQueryItem(STRPTR(t), GETOPERAND(q), StopMiddle, item, true) == 0)
+ {
+ (*nitem)++;
+ StopMiddle++;
+ }
+ }
+
+ return (*nitem > 0) ? StopHigh : NULL;
+}
+
+
+/*
+ * sort QueryOperands by (length, word)
+ */
+static int
+compareQueryOperand(const void *a, const void *b, void *arg)
+{
+ char *operand = (char *) arg;
+ QueryOperand *qa = (*(QueryOperand *const *) a);
+ QueryOperand *qb = (*(QueryOperand *const *) b);
+
+ return tsCompareString(operand + qa->distance, qa->length,
+ operand + qb->distance, qb->length,
+ false);
+}
+
+/*
+ * Returns a sorted, de-duplicated array of QueryOperands in a query.
+ * The returned QueryOperands are pointers to the original QueryOperands
+ * in the query.
+ *
+ * Length of the returned array is stored in *size
+ */
+static QueryOperand **
+SortAndUniqItems(TSQuery q, int *size)
+{
+ char *operand = GETOPERAND(q);
+ QueryItem *item = GETQUERY(q);
+ QueryOperand **res,
+ **ptr,
+ **prevptr;
+
+ ptr = res = (QueryOperand **) palloc(sizeof(QueryOperand *) * *size);
+
+ /* Collect all operands from the tree to res */
+ while ((*size)--)
+ {
+ if (item->type == QI_VAL)
+ {
+ *ptr = (QueryOperand *) item;
+ ptr++;
+ }
+ item++;
+ }
+
+ *size = ptr - res;
+ if (*size < 2)
+ return res;
+
+ qsort_arg(res, *size, sizeof(QueryOperand *), compareQueryOperand, (void *) operand);
+
+ ptr = res + 1;
+ prevptr = res;
+
+ /* remove duplicates */
+ while (ptr - res < *size)
+ {
+ if (compareQueryOperand((void *) ptr, (void *) prevptr, (void *) operand) != 0)
+ {
+ prevptr++;
+ *prevptr = *ptr;
+ }
+ ptr++;
+ }
+
+ *size = prevptr + 1 - res;
+ return res;
+}
+
+static float
+calc_rank_and(const float *w, TSVector t, TSQuery q)
+{
+ WordEntryPosVector **pos;
+ WordEntryPosVector1 posnull;
+ WordEntryPosVector *POSNULL;
+ int i,
+ k,
+ l,
+ p;
+ WordEntry *entry,
+ *firstentry;
+ WordEntryPos *post,
+ *ct;
+ int32 dimt,
+ lenct,
+ dist,
+ nitem;
+ float res = -1.0;
+ QueryOperand **item;
+ int size = q->size;
+
+ item = SortAndUniqItems(q, &size);
+ if (size < 2)
+ {
+ pfree(item);
+ return calc_rank_or(w, t, q);
+ }
+ pos = (WordEntryPosVector **) palloc0(sizeof(WordEntryPosVector *) * q->size);
+
+ /* A dummy WordEntryPos array to use when haspos is false */
+ posnull.npos = 1;
+ posnull.pos[0] = 0;
+ WEP_SETPOS(posnull.pos[0], MAXENTRYPOS - 1);
+ POSNULL = (WordEntryPosVector *) &posnull;
+
+ for (i = 0; i < size; i++)
+ {
+ firstentry = entry = find_wordentry(t, q, item[i], &nitem);
+ if (!entry)
+ continue;
+
+ while (entry - firstentry < nitem)
+ {
+ if (entry->haspos)
+ pos[i] = _POSVECPTR(t, entry);
+ else
+ pos[i] = POSNULL;
+
+ dimt = pos[i]->npos;
+ post = pos[i]->pos;
+ for (k = 0; k < i; k++)
+ {
+ if (!pos[k])
+ continue;
+ lenct = pos[k]->npos;
+ ct = pos[k]->pos;
+ for (l = 0; l < dimt; l++)
+ {
+ for (p = 0; p < lenct; p++)
+ {
+ dist = Abs((int) WEP_GETPOS(post[l]) - (int) WEP_GETPOS(ct[p]));
+ if (dist || (dist == 0 && (pos[i] == POSNULL || pos[k] == POSNULL)))
+ {
+ float curw;
+
+ if (!dist)
+ dist = MAXENTRYPOS;
+ curw = sqrt(wpos(post[l]) * wpos(ct[p]) * word_distance(dist));
+ res = (res < 0) ? curw : 1.0 - (1.0 - res) * (1.0 - curw);
+ }
+ }
+ }
+ }
+
+ entry++;
+ }
+ }
+ pfree(pos);
+ pfree(item);
+ return res;
+}
+
+static float
+calc_rank_or(const float *w, TSVector t, TSQuery q)
+{
+ WordEntry *entry,
+ *firstentry;
+ WordEntryPosVector1 posnull;
+ WordEntryPos *post;
+ int32 dimt,
+ j,
+ i,
+ nitem;
+ float res = 0.0;
+ QueryOperand **item;
+ int size = q->size;
+
+ /* A dummy WordEntryPos array to use when haspos is false */
+ posnull.npos = 1;
+ posnull.pos[0] = 0;
+
+ item = SortAndUniqItems(q, &size);
+
+ for (i = 0; i < size; i++)
+ {
+ float resj,
+ wjm;
+ int32 jm;
+
+ firstentry = entry = find_wordentry(t, q, item[i], &nitem);
+ if (!entry)
+ continue;
+
+ while (entry - firstentry < nitem)
+ {
+ if (entry->haspos)
+ {
+ dimt = POSDATALEN(t, entry);
+ post = POSDATAPTR(t, entry);
+ }
+ else
+ {
+ dimt = posnull.npos;
+ post = posnull.pos;
+ }
+
+ resj = 0.0;
+ wjm = -1.0;
+ jm = 0;
+ for (j = 0; j < dimt; j++)
+ {
+ resj = resj + wpos(post[j]) / ((j + 1) * (j + 1));
+ if (wpos(post[j]) > wjm)
+ {
+ wjm = wpos(post[j]);
+ jm = j;
+ }
+ }
+/*
+ limit (sum(1/i^2),i=1,inf) = pi^2/6
+ resj = sum(wi/i^2),i=1,noccurence,
+ wi - should be sorted desc,
+ don't sort for now, just choose maximum weight. This should be corrected
+ Oleg Bartunov
+*/
+ res = res + (wjm + resj - wjm / ((jm + 1) * (jm + 1))) / 1.64493406685;
+
+ entry++;
+ }
+ }
+ if (size > 0)
+ res = res / size;
+ pfree(item);
+ return res;
+}
+
+static float
+calc_rank(const float *w, TSVector t, TSQuery q, int32 method)
+{
+ QueryItem *item = GETQUERY(q);
+ float res = 0.0;
+ int len;
+
+ if (!t->size || !q->size)
+ return 0.0;
+
+ /* XXX: What about NOT? */
+ res = (item->type == QI_OPR && (item->qoperator.oper == OP_AND ||
+ item->qoperator.oper == OP_PHRASE)) ?
+ calc_rank_and(w, t, q) :
+ calc_rank_or(w, t, q);
+
+ if (res < 0)
+ res = 1e-20f;
+
+ if ((method & RANK_NORM_LOGLENGTH) && t->size > 0)
+ res /= log((double) (cnt_length(t) + 1)) / log(2.0);
+
+ if (method & RANK_NORM_LENGTH)
+ {
+ len = cnt_length(t);
+ if (len > 0)
+ res /= (float) len;
+ }
+
+ /* RANK_NORM_EXTDIST not applicable */
+
+ if ((method & RANK_NORM_UNIQ) && t->size > 0)
+ res /= (float) (t->size);
+
+ if ((method & RANK_NORM_LOGUNIQ) && t->size > 0)
+ res /= log((double) (t->size + 1)) / log(2.0);
+
+ if (method & RANK_NORM_RDIVRPLUS1)
+ res /= (res + 1);
+
+ return res;
+}
+
+static const float *
+getWeights(ArrayType *win)
+{
+ static float ws[lengthof(weights)];
+ int i;
+ float4 *arrdata;
+
+ if (win == NULL)
+ return weights;
+
+ if (ARR_NDIM(win) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("array of weight must be one-dimensional")));
+
+ if (ArrayGetNItems(ARR_NDIM(win), ARR_DIMS(win)) < lengthof(weights))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("array of weight is too short")));
+
+ if (array_contains_nulls(win))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("array of weight must not contain nulls")));
+
+ arrdata = (float4 *) ARR_DATA_PTR(win);
+ for (i = 0; i < lengthof(weights); i++)
+ {
+ ws[i] = (arrdata[i] >= 0) ? arrdata[i] : weights[i];
+ if (ws[i] > 1.0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("weight out of range")));
+ }
+
+ return ws;
+}
+
+Datum
+ts_rank_wttf(PG_FUNCTION_ARGS)
+{
+ ArrayType *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+ TSVector txt = PG_GETARG_TSVECTOR(1);
+ TSQuery query = PG_GETARG_TSQUERY(2);
+ int method = PG_GETARG_INT32(3);
+ float res;
+
+ res = calc_rank(getWeights(win), txt, query, method);
+
+ PG_FREE_IF_COPY(win, 0);
+ PG_FREE_IF_COPY(txt, 1);
+ PG_FREE_IF_COPY(query, 2);
+ PG_RETURN_FLOAT4(res);
+}
+
+Datum
+ts_rank_wtt(PG_FUNCTION_ARGS)
+{
+ ArrayType *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+ TSVector txt = PG_GETARG_TSVECTOR(1);
+ TSQuery query = PG_GETARG_TSQUERY(2);
+ float res;
+
+ res = calc_rank(getWeights(win), txt, query, DEF_NORM_METHOD);
+
+ PG_FREE_IF_COPY(win, 0);
+ PG_FREE_IF_COPY(txt, 1);
+ PG_FREE_IF_COPY(query, 2);
+ PG_RETURN_FLOAT4(res);
+}
+
+Datum
+ts_rank_ttf(PG_FUNCTION_ARGS)
+{
+ TSVector txt = PG_GETARG_TSVECTOR(0);
+ TSQuery query = PG_GETARG_TSQUERY(1);
+ int method = PG_GETARG_INT32(2);
+ float res;
+
+ res = calc_rank(getWeights(NULL), txt, query, method);
+
+ PG_FREE_IF_COPY(txt, 0);
+ PG_FREE_IF_COPY(query, 1);
+ PG_RETURN_FLOAT4(res);
+}
+
+Datum
+ts_rank_tt(PG_FUNCTION_ARGS)
+{
+ TSVector txt = PG_GETARG_TSVECTOR(0);
+ TSQuery query = PG_GETARG_TSQUERY(1);
+ float res;
+
+ res = calc_rank(getWeights(NULL), txt, query, DEF_NORM_METHOD);
+
+ PG_FREE_IF_COPY(txt, 0);
+ PG_FREE_IF_COPY(query, 1);
+ PG_RETURN_FLOAT4(res);
+}
+
+typedef struct
+{
+ union
+ {
+ struct
+ { /* compiled doc representation */
+ QueryItem **items;
+ int16 nitem;
+ } query;
+ struct
+ { /* struct is used for preparing doc
+ * representation */
+ QueryItem *item;
+ WordEntry *entry;
+ } map;
+ } data;
+ WordEntryPos pos;
+} DocRepresentation;
+
+static int
+compareDocR(const void *va, const void *vb)
+{
+ const DocRepresentation *a = (const DocRepresentation *) va;
+ const DocRepresentation *b = (const DocRepresentation *) vb;
+
+ if (WEP_GETPOS(a->pos) == WEP_GETPOS(b->pos))
+ {
+ if (WEP_GETWEIGHT(a->pos) == WEP_GETWEIGHT(b->pos))
+ {
+ if (a->data.map.entry == b->data.map.entry)
+ return 0;
+
+ return (a->data.map.entry > b->data.map.entry) ? 1 : -1;
+ }
+
+ return (WEP_GETWEIGHT(a->pos) > WEP_GETWEIGHT(b->pos)) ? 1 : -1;
+ }
+
+ return (WEP_GETPOS(a->pos) > WEP_GETPOS(b->pos)) ? 1 : -1;
+}
+
+#define MAXQROPOS MAXENTRYPOS
+typedef struct
+{
+ bool operandexists;
+ bool reverseinsert; /* indicates insert order, true means
+ * descending order */
+ uint32 npos;
+ WordEntryPos pos[MAXQROPOS];
+} QueryRepresentationOperand;
+
+typedef struct
+{
+ TSQuery query;
+ QueryRepresentationOperand *operandData;
+} QueryRepresentation;
+
+#define QR_GET_OPERAND_DATA(q, v) \
+ ( (q)->operandData + (((QueryItem*)(v)) - GETQUERY((q)->query)) )
+
+/*
+ * TS_execute callback for matching a tsquery operand to QueryRepresentation
+ */
+static TSTernaryValue
+checkcondition_QueryOperand(void *checkval, QueryOperand *val,
+ ExecPhraseData *data)
+{
+ QueryRepresentation *qr = (QueryRepresentation *) checkval;
+ QueryRepresentationOperand *opData = QR_GET_OPERAND_DATA(qr, val);
+
+ if (!opData->operandexists)
+ return TS_NO;
+
+ if (data)
+ {
+ data->npos = opData->npos;
+ data->pos = opData->pos;
+ if (opData->reverseinsert)
+ data->pos += MAXQROPOS - opData->npos;
+ }
+
+ return TS_YES;
+}
+
+typedef struct
+{
+ int pos;
+ int p;
+ int q;
+ DocRepresentation *begin;
+ DocRepresentation *end;
+} CoverExt;
+
+static void
+resetQueryRepresentation(QueryRepresentation *qr, bool reverseinsert)
+{
+ int i;
+
+ for (i = 0; i < qr->query->size; i++)
+ {
+ qr->operandData[i].operandexists = false;
+ qr->operandData[i].reverseinsert = reverseinsert;
+ qr->operandData[i].npos = 0;
+ }
+}
+
+static void
+fillQueryRepresentationData(QueryRepresentation *qr, DocRepresentation *entry)
+{
+ int i;
+ int lastPos;
+ QueryRepresentationOperand *opData;
+
+ for (i = 0; i < entry->data.query.nitem; i++)
+ {
+ if (entry->data.query.items[i]->type != QI_VAL)
+ continue;
+
+ opData = QR_GET_OPERAND_DATA(qr, entry->data.query.items[i]);
+
+ opData->operandexists = true;
+
+ if (opData->npos == 0)
+ {
+ lastPos = (opData->reverseinsert) ? (MAXQROPOS - 1) : 0;
+ opData->pos[lastPos] = entry->pos;
+ opData->npos++;
+ continue;
+ }
+
+ lastPos = opData->reverseinsert ?
+ (MAXQROPOS - opData->npos) :
+ (opData->npos - 1);
+
+ if (WEP_GETPOS(opData->pos[lastPos]) != WEP_GETPOS(entry->pos))
+ {
+ lastPos = opData->reverseinsert ?
+ (MAXQROPOS - 1 - opData->npos) :
+ (opData->npos);
+
+ opData->pos[lastPos] = entry->pos;
+ opData->npos++;
+ }
+ }
+}
+
+static bool
+Cover(DocRepresentation *doc, int len, QueryRepresentation *qr, CoverExt *ext)
+{
+ DocRepresentation *ptr;
+ int lastpos = ext->pos;
+ bool found = false;
+
+ /*
+ * since this function recurses, it could be driven to stack overflow.
+ * (though any decent compiler will optimize away the tail-recursion.
+ */
+ check_stack_depth();
+
+ resetQueryRepresentation(qr, false);
+
+ ext->p = INT_MAX;
+ ext->q = 0;
+ ptr = doc + ext->pos;
+
+ /* find upper bound of cover from current position, move up */
+ while (ptr - doc < len)
+ {
+ fillQueryRepresentationData(qr, ptr);
+
+ if (TS_execute(GETQUERY(qr->query), (void *) qr,
+ TS_EXEC_EMPTY, checkcondition_QueryOperand))
+ {
+ if (WEP_GETPOS(ptr->pos) > ext->q)
+ {
+ ext->q = WEP_GETPOS(ptr->pos);
+ ext->end = ptr;
+ lastpos = ptr - doc;
+ found = true;
+ }
+ break;
+ }
+ ptr++;
+ }
+
+ if (!found)
+ return false;
+
+ resetQueryRepresentation(qr, true);
+
+ ptr = doc + lastpos;
+
+ /* find lower bound of cover from found upper bound, move down */
+ while (ptr >= doc + ext->pos)
+ {
+ /*
+ * we scan doc from right to left, so pos info in reverse order!
+ */
+ fillQueryRepresentationData(qr, ptr);
+
+ if (TS_execute(GETQUERY(qr->query), (void *) qr,
+ TS_EXEC_EMPTY, checkcondition_QueryOperand))
+ {
+ if (WEP_GETPOS(ptr->pos) < ext->p)
+ {
+ ext->begin = ptr;
+ ext->p = WEP_GETPOS(ptr->pos);
+ }
+ break;
+ }
+ ptr--;
+ }
+
+ if (ext->p <= ext->q)
+ {
+ /*
+ * set position for next try to next lexeme after beginning of found
+ * cover
+ */
+ ext->pos = (ptr - doc) + 1;
+ return true;
+ }
+
+ ext->pos++;
+ return Cover(doc, len, qr, ext);
+}
+
+static DocRepresentation *
+get_docrep(TSVector txt, QueryRepresentation *qr, int *doclen)
+{
+ QueryItem *item = GETQUERY(qr->query);
+ WordEntry *entry,
+ *firstentry;
+ WordEntryPos *post;
+ int32 dimt, /* number of 'post' items */
+ j,
+ i,
+ nitem;
+ int len = qr->query->size * 4,
+ cur = 0;
+ DocRepresentation *doc;
+
+ doc = (DocRepresentation *) palloc(sizeof(DocRepresentation) * len);
+
+ /*
+ * Iterate through query to make DocRepresentation for words and it's
+ * entries satisfied by query
+ */
+ for (i = 0; i < qr->query->size; i++)
+ {
+ QueryOperand *curoperand;
+
+ if (item[i].type != QI_VAL)
+ continue;
+
+ curoperand = &item[i].qoperand;
+
+ firstentry = entry = find_wordentry(txt, qr->query, curoperand, &nitem);
+ if (!entry)
+ continue;
+
+ /* iterations over entries in tsvector */
+ while (entry - firstentry < nitem)
+ {
+ if (entry->haspos)
+ {
+ dimt = POSDATALEN(txt, entry);
+ post = POSDATAPTR(txt, entry);
+ }
+ else
+ {
+ /* ignore words without positions */
+ entry++;
+ continue;
+ }
+
+ while (cur + dimt >= len)
+ {
+ len *= 2;
+ doc = (DocRepresentation *) repalloc(doc, sizeof(DocRepresentation) * len);
+ }
+
+ /* iterations over entry's positions */
+ for (j = 0; j < dimt; j++)
+ {
+ if (curoperand->weight == 0 ||
+ curoperand->weight & (1 << WEP_GETWEIGHT(post[j])))
+ {
+ doc[cur].pos = post[j];
+ doc[cur].data.map.entry = entry;
+ doc[cur].data.map.item = (QueryItem *) curoperand;
+ cur++;
+ }
+ }
+
+ entry++;
+ }
+ }
+
+ if (cur > 0)
+ {
+ DocRepresentation *rptr = doc + 1,
+ *wptr = doc,
+ storage;
+
+ /*
+ * Sort representation in ascending order by pos and entry
+ */
+ qsort((void *) doc, cur, sizeof(DocRepresentation), compareDocR);
+
+ /*
+ * Join QueryItem per WordEntry and it's position
+ */
+ storage.pos = doc->pos;
+ storage.data.query.items = palloc(sizeof(QueryItem *) * qr->query->size);
+ storage.data.query.items[0] = doc->data.map.item;
+ storage.data.query.nitem = 1;
+
+ while (rptr - doc < cur)
+ {
+ if (rptr->pos == (rptr - 1)->pos &&
+ rptr->data.map.entry == (rptr - 1)->data.map.entry)
+ {
+ storage.data.query.items[storage.data.query.nitem] = rptr->data.map.item;
+ storage.data.query.nitem++;
+ }
+ else
+ {
+ *wptr = storage;
+ wptr++;
+ storage.pos = rptr->pos;
+ storage.data.query.items = palloc(sizeof(QueryItem *) * qr->query->size);
+ storage.data.query.items[0] = rptr->data.map.item;
+ storage.data.query.nitem = 1;
+ }
+
+ rptr++;
+ }
+
+ *wptr = storage;
+ wptr++;
+
+ *doclen = wptr - doc;
+ return doc;
+ }
+
+ pfree(doc);
+ return NULL;
+}
+
+static float4
+calc_rank_cd(const float4 *arrdata, TSVector txt, TSQuery query, int method)
+{
+ DocRepresentation *doc;
+ int len,
+ i,
+ doclen = 0;
+ CoverExt ext;
+ double Wdoc = 0.0;
+ double invws[lengthof(weights)];
+ double SumDist = 0.0,
+ PrevExtPos = 0.0;
+ int NExtent = 0;
+ QueryRepresentation qr;
+
+
+ for (i = 0; i < lengthof(weights); i++)
+ {
+ invws[i] = ((double) ((arrdata[i] >= 0) ? arrdata[i] : weights[i]));
+ if (invws[i] > 1.0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("weight out of range")));
+ invws[i] = 1.0 / invws[i];
+ }
+
+ qr.query = query;
+ qr.operandData = (QueryRepresentationOperand *)
+ palloc0(sizeof(QueryRepresentationOperand) * query->size);
+
+ doc = get_docrep(txt, &qr, &doclen);
+ if (!doc)
+ {
+ pfree(qr.operandData);
+ return 0.0;
+ }
+
+ MemSet(&ext, 0, sizeof(CoverExt));
+ while (Cover(doc, doclen, &qr, &ext))
+ {
+ double Cpos = 0.0;
+ double InvSum = 0.0;
+ double CurExtPos;
+ int nNoise;
+ DocRepresentation *ptr = ext.begin;
+
+ while (ptr <= ext.end)
+ {
+ InvSum += invws[WEP_GETWEIGHT(ptr->pos)];
+ ptr++;
+ }
+
+ Cpos = ((double) (ext.end - ext.begin + 1)) / InvSum;
+
+ /*
+ * if doc are big enough then ext.q may be equal to ext.p due to limit
+ * of positional information. In this case we approximate number of
+ * noise word as half cover's length
+ */
+ nNoise = (ext.q - ext.p) - (ext.end - ext.begin);
+ if (nNoise < 0)
+ nNoise = (ext.end - ext.begin) / 2;
+ Wdoc += Cpos / ((double) (1 + nNoise));
+
+ CurExtPos = ((double) (ext.q + ext.p)) / 2.0;
+ if (NExtent > 0 && CurExtPos > PrevExtPos /* prevent division by
+ * zero in a case of
+ * multiple lexize */ )
+ SumDist += 1.0 / (CurExtPos - PrevExtPos);
+
+ PrevExtPos = CurExtPos;
+ NExtent++;
+ }
+
+ if ((method & RANK_NORM_LOGLENGTH) && txt->size > 0)
+ Wdoc /= log((double) (cnt_length(txt) + 1));
+
+ if (method & RANK_NORM_LENGTH)
+ {
+ len = cnt_length(txt);
+ if (len > 0)
+ Wdoc /= (double) len;
+ }
+
+ if ((method & RANK_NORM_EXTDIST) && NExtent > 0 && SumDist > 0)
+ Wdoc /= ((double) NExtent) / SumDist;
+
+ if ((method & RANK_NORM_UNIQ) && txt->size > 0)
+ Wdoc /= (double) (txt->size);
+
+ if ((method & RANK_NORM_LOGUNIQ) && txt->size > 0)
+ Wdoc /= log((double) (txt->size + 1)) / log(2.0);
+
+ if (method & RANK_NORM_RDIVRPLUS1)
+ Wdoc /= (Wdoc + 1);
+
+ pfree(doc);
+
+ pfree(qr.operandData);
+
+ return (float4) Wdoc;
+}
+
+Datum
+ts_rankcd_wttf(PG_FUNCTION_ARGS)
+{
+ ArrayType *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+ TSVector txt = PG_GETARG_TSVECTOR(1);
+ TSQuery query = PG_GETARG_TSQUERY(2);
+ int method = PG_GETARG_INT32(3);
+ float res;
+
+ res = calc_rank_cd(getWeights(win), txt, query, method);
+
+ PG_FREE_IF_COPY(win, 0);
+ PG_FREE_IF_COPY(txt, 1);
+ PG_FREE_IF_COPY(query, 2);
+ PG_RETURN_FLOAT4(res);
+}
+
+Datum
+ts_rankcd_wtt(PG_FUNCTION_ARGS)
+{
+ ArrayType *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+ TSVector txt = PG_GETARG_TSVECTOR(1);
+ TSQuery query = PG_GETARG_TSQUERY(2);
+ float res;
+
+ res = calc_rank_cd(getWeights(win), txt, query, DEF_NORM_METHOD);
+
+ PG_FREE_IF_COPY(win, 0);
+ PG_FREE_IF_COPY(txt, 1);
+ PG_FREE_IF_COPY(query, 2);
+ PG_RETURN_FLOAT4(res);
+}
+
+Datum
+ts_rankcd_ttf(PG_FUNCTION_ARGS)
+{
+ TSVector txt = PG_GETARG_TSVECTOR(0);
+ TSQuery query = PG_GETARG_TSQUERY(1);
+ int method = PG_GETARG_INT32(2);
+ float res;
+
+ res = calc_rank_cd(getWeights(NULL), txt, query, method);
+
+ PG_FREE_IF_COPY(txt, 0);
+ PG_FREE_IF_COPY(query, 1);
+ PG_RETURN_FLOAT4(res);
+}
+
+Datum
+ts_rankcd_tt(PG_FUNCTION_ARGS)
+{
+ TSVector txt = PG_GETARG_TSVECTOR(0);
+ TSQuery query = PG_GETARG_TSQUERY(1);
+ float res;
+
+ res = calc_rank_cd(getWeights(NULL), txt, query, DEF_NORM_METHOD);
+
+ PG_FREE_IF_COPY(txt, 0);
+ PG_FREE_IF_COPY(query, 1);
+ PG_RETURN_FLOAT4(res);
+}