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