xapian-core  2.1.0
orpostlist.cc
Go to the documentation of this file.
1 
4 /* Copyright 2017,2022 Olly Betts
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see
18  * <https://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 
23 #include "orpostlist.h"
24 
25 #include "andmaybepostlist.h"
26 #include "andpostlist.h"
27 #include "min_non_zero.h"
28 #include "postlisttree.h"
29 
30 #include <algorithm>
31 
32 using namespace std;
33 
34 template<typename T>
35 static void
36 estimate_or_assuming_indep(double a, double af, double al,
37  double b, double bf, double bl,
38  T& res)
39 {
40  // Clamp estimates to range lengths.
41  a = min(a, al - af + 1.0);
42  b = min(b, bl - bf + 1.0);
43  AssertRel(a,>=,0);
44  AssertRel(b,>=,0);
45 
46  if (al < bf || bl < af) {
47  // Disjoint ranges.
48  res = static_cast<T>(a + b + 0.5);
49  return;
50  }
51 
52  // Arrange for af <= bf.
53  if (af > bf) {
54  swap(a, b);
55  swap(af, bf);
56  swap(al, bl);
57  }
58 
59  // Arrange for al <= bl.
60  if (al > bl) {
61  bf += (al - bl);
62  bl = al;
63  }
64 
65  double arate = a / (al - af + 1);
66  double brate = b / (bl - bf + 1);
67 
68  double r = arate * (bf - af) +
69  brate * (bl - al) +
70  (arate + brate - arate * brate) * (al - bf + 1);
71  res = static_cast<T>(r + 0.5);
72 }
73 
75  PostListTree* pltree_)
76  : l(left), r(right), pltree(pltree_)
77 {
78  auto l_tf_est = l->get_termfreq();
79  auto r_tf_est = r->get_termfreq();
80  Xapian::docid l_first = 1, l_last = Xapian::docid(-1);
81  Xapian::docid r_first = 1, r_last = Xapian::docid(-1);
82  l->get_docid_range(l_first, l_last);
83  r->get_docid_range(r_first, r_last);
84  if (l_last < l_first) {
85  l_last = 0;
86  l_first = 1;
87  }
88  if (r_last < r_first) {
89  r_last = 0;
90  r_first = 1;
91  }
92  estimate_or_assuming_indep(l_tf_est, l_first, l_last,
93  r_tf_est, r_first, r_last,
94  termfreq);
95 }
96 
97 PostList*
99  double w_min,
100  bool* valid_ptr)
101 {
102  l = new AndPostList(l, r, l_max, r_max, pltree, termfreq);
103  r = NULL;
104  PostList* result;
105  if (valid_ptr) {
106  result = l->check(did, w_min, *valid_ptr);
107  } else {
108  result = l->skip_to(did, w_min);
109  }
110  if (!result) {
111  result = l;
112  l = NULL;
113  }
114  pltree->force_recalc();
115  return result;
116 }
117 
118 PostList*
120  PostList* right,
121  Xapian::docid did,
122  double w_min,
123  bool* valid_ptr)
124 {
125  if (l != left) swap(l_max, r_max);
126  l = new AndMaybePostList(left, right, l_max, r_max, pltree);
127  r = NULL;
128  PostList* result;
129  if (valid_ptr) {
130  result = l->check(did, w_min, *valid_ptr);
131  } else {
132  result = l->skip_to(did, w_min);
133  }
134  if (!result) {
135  result = l;
136  l = NULL;
137  }
138  pltree->force_recalc();
139  return result;
140 }
141 
144 {
145  // Handle l_did or r_did being zero correctly (which means the last call on
146  // that side was a check() which came back !valid).
147  return min_non_zero(l_did, r_did);
148 }
149 
150 double
152  Xapian::termcount unique_terms,
153  Xapian::termcount wdfdocmax) const
154 {
155  if (r_did == 0 || l_did < r_did)
156  return l->get_weight(doclen, unique_terms, wdfdocmax);
157  if (l_did == 0 || l_did > r_did)
158  return r->get_weight(doclen, unique_terms, wdfdocmax);
159  return l->get_weight(doclen, unique_terms, wdfdocmax) +
160  r->get_weight(doclen, unique_terms, wdfdocmax);
161 }
162 
163 double
165 {
166  l_max = l->recalc_maxweight();
167  r_max = r->recalc_maxweight();
168  return l_max + r_max;
169 }
170 
171 PostList*
172 OrPostList::next(double w_min)
173 {
174  if (w_min > l_max) {
175  if (w_min > r_max) {
176  // Work out the smallest docid which the AND could match at.
177  Xapian::docid did;
178  if (l_did == r_did || r_did == 0) {
179  did = l_did + 1;
180  } else if (l_did == 0) {
181  did = r_did + 1;
182  } else {
183  // The OR last matched at min(l_did, r_did), so the AND could
184  // match at the max().
185  did = max(l_did, r_did);
186  }
187  return decay_to_and(did, w_min);
188  }
189  // Work out the smallest docid which r AND_MAYBE l could match at.
190  Xapian::docid did;
191  if (r_did == 0) {
192  did = l_did + 1;
193  } else if (l_did - 1 >= r_did - 1) {
194  did = r_did + 1;
195  } else {
196  // l_did and r_did both non zero and l_did < r_did.
197  did = r_did;
198  }
199  return decay_to_andmaybe(r, l, did, w_min);
200  }
201  if (w_min > r_max) {
202  // Work out the smallest docid which l AND_MAYBE r could match at.
203  Xapian::docid did;
204  if (l_did == 0) {
205  did = r_did + 1;
206  } else if (r_did - 1 >= l_did - 1) {
207  did = l_did + 1;
208  } else {
209  // l_did and r_did both non zero and r_did < l_did.
210  did = l_did;
211  }
212  return decay_to_andmaybe(l, r, did, w_min);
213  }
214 
215  // We always advance_l if l_did is 0, and similarly for advance_r.
216  bool advance_l = (l_did <= r_did);
217  bool advance_r = (l_did >= r_did);
218 
219  if (advance_l) {
220  PostList* result = l->next(w_min - r_max);
221  if (result) {
222  delete l;
223  l = result;
224  }
225  }
226 
227  if (advance_r) {
228  PostList* result = r->next(w_min - l_max);
229  if (result) {
230  delete r;
231  r = result;
232  }
233  }
234 
235  if (advance_l) {
236  if (l->at_end()) {
237  PostList* result = r;
238  r = NULL;
239  pltree->force_recalc();
240  return result;
241  }
242  }
243 
244  if (advance_r) {
245  if (r->at_end()) {
246  PostList* result = l;
247  l = NULL;
248  pltree->force_recalc();
249  return result;
250  }
251  }
252 
253  if (advance_l) {
254  l_did = l->get_docid();
255  }
256 
257  if (advance_r) {
258  r_did = r->get_docid();
259  }
260 
261  return NULL;
262 }
263 
264 PostList*
266 {
267  // We always advance_l if l_did is 0, and similarly for advance_r.
268  bool advance_l = (did > l_did);
269  bool advance_r = (did > r_did);
270  if (!advance_l && !advance_r)
271  return NULL;
272 
273  if (w_min > l_max) {
274  if (w_min > r_max)
275  return decay_to_and(did, w_min);
276  return decay_to_andmaybe(r, l, did, w_min);
277  }
278  if (w_min > r_max) {
279  return decay_to_andmaybe(l, r, did, w_min);
280  }
281 
282  if (advance_l) {
283  PostList* result = l->skip_to(did, w_min - r_max);
284  if (result) {
285  delete l;
286  l = result;
287  }
288  }
289 
290  if (advance_r) {
291  PostList* result = r->skip_to(did, w_min - l_max);
292  if (result) {
293  delete r;
294  r = result;
295  }
296  }
297 
298  if (advance_l) {
299  if (l->at_end()) {
300  PostList* result = r;
301  r = NULL;
302  pltree->force_recalc();
303  return result;
304  }
305  }
306 
307  if (advance_r) {
308  if (r->at_end()) {
309  PostList* result = l;
310  l = NULL;
311  pltree->force_recalc();
312  return result;
313  }
314  }
315 
316  if (advance_l) {
317  l_did = l->get_docid();
318  }
319 
320  if (advance_r) {
321  r_did = r->get_docid();
322  }
323 
324  return NULL;
325 }
326 
327 PostList*
328 OrPostList::check(Xapian::docid did, double w_min, bool& valid)
329 {
330  bool advance_l = (did > l_did);
331  bool advance_r = (did > r_did);
332  if (!advance_l && !advance_r) {
333  // A call to check() which steps back isn't valid, so if we get here
334  // then did should be equal to at least one of l_did or r_did.
335  Assert(did == l_did || did == r_did);
336  valid = true;
337  return NULL;
338  }
339 
340  if (w_min > l_max) {
341  valid = true;
342  if (w_min > r_max)
343  return decay_to_and(did, w_min, &valid);
344  return decay_to_andmaybe(r, l, did, w_min, &valid);
345  }
346  if (w_min > r_max) {
347  valid = true;
348  return decay_to_andmaybe(l, r, did, w_min, &valid);
349  }
350 
351  if (advance_l) {
352  bool l_valid;
353  PostList* result = l->check(did, w_min - r_max, l_valid);
354  if (result) {
355  Assert(l_valid);
356  delete l;
357  l = result;
358  } else if (!l_valid) {
359  l_did = 0;
360  advance_l = false;
361  }
362  }
363 
364  if (advance_r) {
365  bool r_valid;
366  PostList* result = r->check(did, w_min - l_max, r_valid);
367  if (result) {
368  Assert(r_valid);
369  delete r;
370  r = result;
371  } else if (!r_valid) {
372  r_did = 0;
373  advance_r = false;
374  }
375  }
376 
377  if (advance_l) {
378  if (l->at_end()) {
379  PostList* result = r;
380  r = NULL;
381  pltree->force_recalc();
382  valid = true;
383  return result;
384  }
385  }
386 
387  if (advance_r) {
388  if (r->at_end()) {
389  PostList* result = l;
390  l = NULL;
391  pltree->force_recalc();
392  valid = true;
393  return result;
394  }
395  }
396 
397  if (advance_l) {
398  l_did = l->get_docid();
399  }
400 
401  if (advance_r) {
402  r_did = r->get_docid();
403  }
404 
405  valid = (l_did == did || r_did == did) || (l_did != 0 && r_did != 0);
406 
407  return NULL;
408 }
409 
410 bool
412 {
413  // We never need to return true here - if one child reaches at_end(), we
414  // prune to leave the other, and if both children reach at_end() together,
415  // we prune to leave one of them which will then indicate at_end() for us.
416  return false;
417 }
418 
419 void
421 {
422  l->get_docid_range(first, last);
423  Xapian::docid first2 = 1, last2 = Xapian::docid(-1);
424  r->get_docid_range(first2, last2);
425  first = min(first, first2);
426  last = max(last, last2);
427 }
428 
429 std::string
431 {
432  string desc = "OrPostList(";
433  desc += l->get_description();
434  desc += ", ";
435  desc += r->get_description();
436  desc += ')';
437  return desc;
438 }
439 
442 {
443  if (r_did == 0 || l_did < r_did)
444  return l->get_wdf();
445  if (l_did == 0 || l_did > r_did)
446  return r->get_wdf();
447  return l->get_wdf() + r->get_wdf();
448 }
449 
452 {
453  if (r_did == 0 || l_did < r_did)
454  return l->count_matching_subqs();
455  if (l_did == 0 || l_did > r_did)
456  return r->count_matching_subqs();
458 }
459 
460 void
462 {
463  if (l_did - 1 <= r_did - 1)
464  l->gather_position_lists(orposlist);
465  if (l_did - 1 >= r_did - 1)
466  r->gather_position_lists(orposlist);
467 }
PostList class implementing Query::OP_AND_MAYBE.
N-way AND postlist.
PostList class implementing Query::OP_AND_MAYBE.
N-way AND postlist.
Definition: andpostlist.h:32
Xapian::docid r_did
Definition: orpostlist.h:49
PostList * check(Xapian::docid did, double w_min, bool &valid)
Check if the specified docid occurs in this postlist.
Definition: orpostlist.cc:328
Xapian::termcount get_wdf() const
Return the wdf for the document at the current position.
Definition: orpostlist.cc:441
double recalc_maxweight()
Recalculate the upper bound on what get_weight() can return.
Definition: orpostlist.cc:164
OrPostList(const OrPostList &)=delete
Don't allow copying.
Xapian::termcount count_matching_subqs() const
Count the number of leaf subqueries which match at the current position.
Definition: orpostlist.cc:451
PostList * skip_to(Xapian::docid did, double w_min)
Skip forward to the specified docid.
Definition: orpostlist.cc:265
PostList * decay_to_andmaybe(PostList *left, PostList *right, Xapian::docid did, double w_min, bool *valid_ptr=NULL)
Definition: orpostlist.cc:119
PostList * decay_to_and(Xapian::docid did, double w_min, bool *valid_ptr=NULL)
Definition: orpostlist.cc:98
void get_docid_range(Xapian::docid &first, Xapian::docid &last) const
Get the bounds on the range of docids this PostList can return.
Definition: orpostlist.cc:420
PostList * l
Left side.
Definition: orpostlist.h:42
Xapian::docid get_docid() const
Return the current docid.
Definition: orpostlist.cc:143
std::string get_description() const
Return a string description of this object.
Definition: orpostlist.cc:430
double get_weight(Xapian::termcount doclen, Xapian::termcount unique_terms, Xapian::termcount wdfdocmax) const
Return the weight contribution for the current position.
Definition: orpostlist.cc:151
Xapian::docid l_did
Definition: orpostlist.h:47
double l_max
Definition: orpostlist.h:51
double r_max
Definition: orpostlist.h:53
void gather_position_lists(OrPositionList *orposlist)
Gather PositionList* objects for a subtree.
Definition: orpostlist.cc:461
PostListTree * pltree
Definition: orpostlist.h:55
PostList * r
Right side.
Definition: orpostlist.h:45
bool at_end() const
Return true if the current position is past the last entry in this list.
Definition: orpostlist.cc:411
void force_recalc()
Definition: postlisttree.h:154
Abstract base class for postlists.
Definition: postlist.h:40
virtual PostList * skip_to(Xapian::docid did, double w_min)=0
Skip forward to the specified docid.
virtual PostList * next(double w_min)=0
Advance the current position to the next document in the postlist.
Xapian::doccount get_termfreq() const
Get an estimate of the number of documents this PostList will return.
Definition: postlist.h:67
virtual Xapian::termcount get_wdf() const
Return the wdf for the document at the current position.
Definition: postlist.cc:34
virtual Xapian::docid get_docid() const =0
Return the current docid.
virtual double recalc_maxweight()=0
Recalculate the upper bound on what get_weight() can return.
virtual bool at_end() const =0
Return true if the current position is past the last entry in this list.
PostList * next()
Advance the current position to the next document in the postlist.
Definition: postlist.h:168
virtual void gather_position_lists(OrPositionList *orposlist)
Gather PositionList* objects for a subtree.
Definition: postlist.cc:66
virtual PostList * check(Xapian::docid did, double w_min, bool &valid)
Check if the specified docid occurs in this postlist.
Definition: postlist.cc:52
virtual void get_docid_range(docid &first, docid &last) const
Get the bounds on the range of docids this PostList can return.
Definition: postlist.cc:72
virtual std::string get_description() const =0
Return a string description of this object.
Xapian::doccount termfreq
Estimate of the number of documents this PostList will return.
Definition: postlist.h:52
virtual double get_weight(Xapian::termcount doclen, Xapian::termcount unique_terms, Xapian::termcount wdfdocmax) const =0
Return the weight contribution for the current position.
virtual Xapian::termcount count_matching_subqs() const
Count the number of leaf subqueries which match at the current position.
Definition: postlist.cc:59
Return the smaller of two numbers which isn't zero.
constexpr std::enable_if_t< std::is_unsigned_v< T >, T > min_non_zero(const T &a, const T &b)
Return the smaller of two unsigned integers which isn't zero.
Definition: min_non_zero.h:39
unsigned XAPIAN_TERMCOUNT_BASE_TYPE termcount
A counts of terms.
Definition: types.h:64
unsigned XAPIAN_DOCID_BASE_TYPE docid
A unique identifier for a document.
Definition: types.h:51
#define AssertRel(A, REL, B)
Definition: omassert.h:123
#define Assert(COND)
Definition: omassert.h:122
static void estimate_or_assuming_indep(double a, double af, double al, double b, double bf, double bl, T &res)
Definition: orpostlist.cc:36
PostList class implementing Query::OP_OR.
Class for managing a tree of PostList objects.