xapian-core  2.1.0
queryinternal.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2007-2026 Olly Betts
5  * Copyright (C) 2008,2009 Lemur Consulting Ltd
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 
24 #include "queryinternal.h"
25 
26 #include "xapian/error.h"
27 #include "xapian/postingsource.h"
28 #include "xapian/query.h"
29 #include "xapian/unicode.h"
30 
31 #include "api/editdistance.h"
32 #include "backends/postlist.h"
33 #include "clamp_cast.h"
34 #include "heap.h"
36 #include "matcher/andnotpostlist.h"
37 #include "matcher/andpostlist.h"
38 #include "matcher/boolorpostlist.h"
41 #include "matcher/maxpostlist.h"
42 #include "matcher/nearpostlist.h"
43 #include "matcher/orpospostlist.h"
44 #include "matcher/orpostlist.h"
45 #include "matcher/phrasepostlist.h"
46 #include "matcher/queryoptimiser.h"
49 #include "matcher/xorpostlist.h"
50 #include "pack.h"
51 #include "serialise-double.h"
52 #include "stringutils.h"
53 #include "termlist.h"
54 
55 #include "debuglog.h"
56 #include "omassert.h"
57 #include "str.h"
58 #include "stringutils.h"
60 
61 #include <algorithm>
62 #include <limits>
63 #include <list>
64 #include <memory>
65 #include <string>
66 #include <string_view>
67 #include <unordered_set>
68 #include <vector>
69 
70 using namespace std;
71 
72 static constexpr unsigned MAX_UTF_8_CHARACTER_LENGTH = 4;
73 
77 
78 namespace Xapian {
79 
80 namespace Internal {
81 
90 struct CmpMaxOrTerms {
92  bool operator()(PostList* a, PostList* b) {
93 #if (defined(__i386__) && !defined(__SSE_MATH__)) || \
94  defined(__mc68000__) || defined(__mc68010__) || \
95  defined(__mc68020__) || defined(__mc68030__)
96  // On some architectures, most common of which is x86, floating point
97  // values are calculated and stored in registers with excess precision.
98  // If the two recalc_maxweight() calls below return identical values in a
99  // register, the excess precision may be dropped for one of them but
100  // not the other (e.g. because the compiler saves the first calculated
101  // weight to memory while calculating the second, then reloads it to
102  // compare). This leads to both a > b and b > a being true, which
103  // violates the antisymmetry property of the strict weak ordering
104  // required by nth_element(). This can have serious consequences (e.g.
105  // segfaults).
106  //
107  // Note that m68k only has excess precision in earlier models - 68040
108  // and later are OK:
109  // https://gcc.gnu.org/ml/gcc-patches/2008-11/msg00105.html
110  //
111  // To avoid this, we store each result in a volatile double prior to
112  // comparing them. This means that the result of this test should
113  // match that on other architectures with the same double format (which
114  // is desirable), and actually has less overhead than rounding both
115  // results to float (which is another approach which works).
116  volatile double a_max_wt = a->recalc_maxweight();
117  volatile double b_max_wt = b->recalc_maxweight();
118  return a_max_wt > b_max_wt;
119 #else
120  return a->recalc_maxweight() > b->recalc_maxweight();
121 #endif
122  }
123 };
124 
128  bool operator()(const PostList* a,
129  const PostList* b) const {
130  return a->get_termfreq() > b->get_termfreq();
131  }
132 };
133 
134 class Context {
135  protected:
137 
138  vector<PostList*> pls;
139 
141 
142  vector<TermFreqs> termfreqs_list;
143 
146 
148  Xapian::docid last = 0;
149 
150  public:
151  Context(QueryOptimiser* qopt_, size_t reserve)
152  : qopt(qopt_), estimates(reserve) {
153  pls.reserve(reserve);
154  }
155 
157  shrink(0);
158  }
159 
160  Xapian::docid get_first() const { return first; }
161 
162  Xapian::docid get_last() const { return last; }
163 
164  void add_termfreqs(TermFreqs* termfreqs) {
165  if (termfreqs) termfreqs_list.emplace_back(*termfreqs);
166  }
167 
168  void add_postlist(PostList* pl, EstimateOp* estimate,
169  TermFreqs* termfreqs) {
170  add_termfreqs(termfreqs);
171  if (pl) {
172  pls.emplace_back(pl);
173  // estimate can be NULL e.g. under the RHS of OP_AND_MAYBE.
174  if (estimate) estimates.push_back(estimate);
175  // Take the union of the docid ranges, which is suitable for
176  // OrContext and XorContext. AndContext() implements its own
177  // version of add_postlist() which takes the intersection.
178  Xapian::docid f = 1;
180  pl->get_docid_range(f, l);
181  first = std::min(first, f);
182  last = std::max(last, l);
183  } else {
184  Assert(!estimate);
185  }
186  }
187 
189  add_postlist(p.pl, p.est.release(), termfreqs);
190  }
191 
192  bool empty() const {
193  return pls.empty();
194  }
195 
197  return Xapian::termcount(pls.size());
198  }
199 
200  void shrink(size_t new_size) {
201  AssertRel(new_size, <=, pls.size());
202  if (new_size >= pls.size())
203  return;
204 
205  for (auto&& i = pls.begin() + new_size; i != pls.end(); ++i) {
206  qopt->destroy_postlist(*i);
207  }
208  pls.resize(new_size);
209  estimates.erase(estimates.begin() + new_size, estimates.end());
210  }
211 
213  void expand_wildcard(const QueryWildcard* query,
214  double factor,
215  TermFreqs* termfreqs);
216 
218  void expand_edit_distance(const QueryEditDistance* query,
219  double factor,
220  TermFreqs* termfreqs);
221 };
222 
223 inline void
224 Context::expand_wildcard(const QueryWildcard* query,
225  double factor,
226  TermFreqs* termfreqs)
227 {
228  unique_ptr<TermList> t(qopt->db.open_allterms(query->get_fixed_prefix()));
229  bool skip_ucase = query->get_fixed_prefix().empty();
230  auto max_type = query->get_max_type();
231  Xapian::termcount expansions_left = query->get_max_expansion();
232  // If there's no expansion limit, set expansions_left to the maximum
233  // value it can hold.
234  if (expansions_left == 0)
235  expansions_left = numeric_limits<decltype(expansions_left)>::max();
236  while (true) {
237  TermList* ret = t->next();
238 done_skip_to:
239  if (ret) {
240  // Pruning shouldn't be possible, as this is iterating allterms for
241  // a single shard.
242  Assert(ret == t.get());
243  // End of entries.
244  break;
245  }
246 
247  const string & term = t->get_termname();
248  if (skip_ucase && term[0] >= 'A') {
249  // If there's a leading wildcard then skip terms that start
250  // with A-Z, as we don't want the expansion to include prefixed
251  // terms.
252  //
253  // This assumes things about the structure of terms which the
254  // Query class otherwise doesn't need to care about, but it
255  // seems hard to avoid here.
256  skip_ucase = false;
257  if (term[0] <= 'Z') {
258  static_assert('Z' + 1 == '[', "'Z' + 1 == '['");
259  ret = t->skip_to("[");
260  goto done_skip_to;
261  }
262  }
263 
264  if (!query->test_prefix_known(term)) continue;
265 
267  if (expansions_left == 0) {
268  if (max_type == Xapian::Query::WILDCARD_LIMIT_FIRST)
269  break;
270  string msg("Wildcard ");
271  msg += query->get_pattern();
272  if (query->get_just_flags() == 0)
273  msg += '*';
274  msg += " expands to more than ";
275  msg += str(query->get_max_expansion());
276  msg += " terms";
277  throw Xapian::WildcardError(msg);
278  }
279  --expansions_left;
280  }
281 
282  add_postlist(qopt->open_lazy_post_list(term, 1, factor), NULL);
283  // Generate a single EstimateOp to avoid overhead for wildcards
284  // which expand to a lot of terms? (FIXME)
285  }
286 
288  auto set_size = query->get_max_expansion();
289  if (size() > set_size) {
290  auto begin = pls.begin();
291  nth_element(begin, begin + set_size - 1, pls.end(),
293  shrink(set_size);
294  }
295  }
296 
297  // Now register the postlists we're actually using for stats.
298  for (auto pl : pls) {
299  // FIXME: LocalSubMatch::open_lazy_post_list() above returns a
300  // PostList* that actually points to a LeafPostList. It would be
301  // better to find a way to be more type-safe here and avoid need to
302  // cast back.
303  qopt->register_lazy_postlist_for_stats(static_cast<LeafPostList*>(pl),
304  termfreqs);
305  add_termfreqs(termfreqs);
306  }
307 }
308 
309 inline void
310 Context::expand_edit_distance(const QueryEditDistance* query,
311  double factor,
312  TermFreqs* termfreqs)
313 {
314  string pfx(query->get_pattern(), 0, query->get_fixed_prefix_len());
315  unique_ptr<TermList> t(qopt->db.open_allterms(pfx));
316  bool skip_ucase = pfx.empty();
317  auto max_type = query->get_max_type();
318  Xapian::termcount expansions_left = query->get_max_expansion();
319  // If there's no expansion limit, set expansions_left to the maximum
320  // value it can hold.
321  if (expansions_left == 0)
322  expansions_left = numeric_limits<decltype(expansions_left)>::max();
323  while (true) {
324  TermList* res = t->next();
325 done_skip_to:
326  if (res) {
327  // Pruning shouldn't be possible, as this is iterating allterms for
328  // a single shard.
329  Assert(res == t.get());
330  // Out of entries.
331  break;
332  }
333 
334  const string& term = t->get_termname();
335  if (!startswith(term, pfx))
336  break;
337  if (skip_ucase && term[0] >= 'A') {
338  // Skip terms that start with A-Z, as we don't want the expansion
339  // to include prefixed terms.
340  //
341  // This assumes things about the structure of terms which the
342  // Query class otherwise doesn't need to care about, but it
343  // seems hard to avoid here.
344  skip_ucase = false;
345  if (term[0] <= 'Z') {
346  static_assert('Z' + 1 == '[', "'Z' + 1 == '['");
347  res = t->skip_to("[");
348  goto done_skip_to;
349  }
350  }
351 
352  if (!query->test(term)) continue;
353 
355  if (expansions_left == 0) {
356  if (max_type == Xapian::Query::WILDCARD_LIMIT_FIRST)
357  break;
358  string msg("Edit distance ");
359  msg += query->get_pattern();
360  msg += '~';
361  msg += str(query->get_threshold());
362  msg += " expands to more than ";
363  msg += str(query->get_max_expansion());
364  msg += " terms";
365  throw Xapian::WildcardError(msg);
366  }
367  --expansions_left;
368  }
369 
370  add_postlist(qopt->open_lazy_post_list(term, 1, factor), NULL);
371  }
372 
374  auto set_size = query->get_max_expansion();
375  if (size() > set_size) {
376  auto begin = pls.begin();
377  nth_element(begin, begin + set_size - 1, pls.end(),
379  shrink(set_size);
380  }
381  }
382 
383  // Now register the postlists we're actually using for stats.
384  for (auto pl : pls) {
385  // FIXME: Be more typesafe?
386  qopt->register_lazy_postlist_for_stats(static_cast<LeafPostList*>(pl),
387  termfreqs);
388  add_termfreqs(termfreqs);
389  }
390 }
391 
392 class OrContext : public Context {
393  public:
394  OrContext(QueryOptimiser* qopt_, size_t reserve)
395  : Context(qopt_, reserve) { }
396 
397  void estimate_termfreqs(TermFreqs* termfreqs);
398 
400  void select_elite_set(size_t set_size, size_t out_of);
401 
402  PostListAndEstimate postlist(TermFreqs* termfreqs, bool bool_or = false);
403 
404  PostListAndEstimate postlist_max();
405 };
406 
407 void
408 OrContext::estimate_termfreqs(TermFreqs* termfreqs)
409 {
410  Assert(termfreqs);
411 
412  Assert(!termfreqs_list.empty());
413 
414  // We calculate the estimate assuming independence. The simplest
415  // way to calculate this seems to be a series of (n - 1) pairwise
416  // calculations, which gives the same answer regardless of the order.
417  const TermFreqs& freqs = termfreqs_list[0];
418  auto& stats = *qopt->get_stats();
419 
420  // Our caller should have ensured this.
421  Assert(stats.collection_size);
422  double scale = 1.0 / stats.collection_size;
423  double P_est = freqs.termfreq * scale;
424  double rtf_scale = 0.0;
425  if (stats.rset_size != 0) {
426  rtf_scale = 1.0 / stats.rset_size;
427  }
428  double Pr_est = freqs.reltermfreq * rtf_scale;
429  // If total_length is 0, cf must always be 0 so cf_scale is irrelevant.
430  double cf_scale = 0.0;
431  if (usual(stats.total_length != 0)) {
432  cf_scale = 1.0 / stats.total_length;
433  }
434  double Pc_est = freqs.collfreq * cf_scale;
435 
436  for (size_t i = 1; i < termfreqs_list.size(); ++i) {
437  const TermFreqs& f = termfreqs_list[i];
438  double P_i = f.termfreq * scale;
439  P_est += P_i - P_est * P_i;
440  double Pc_i = f.collfreq * cf_scale;
441  Pc_est += Pc_i - Pc_est * Pc_i;
442  // If the rset is empty, Pr_est should be 0 already, so leave
443  // it alone.
444  if (stats.rset_size != 0) {
445  double Pr_i = f.reltermfreq * rtf_scale;
446  Pr_est += Pr_i - Pr_est * Pr_i;
447  }
448  }
449 
450  *termfreqs =
451  TermFreqs(Xapian::doccount(P_est * stats.collection_size + 0.5),
452  Xapian::doccount(Pr_est * stats.rset_size + 0.5),
453  Xapian::termcount(Pc_est * stats.total_length + 0.5));
454 }
455 
456 void
457 OrContext::select_elite_set(size_t set_size, size_t out_of)
458 {
459  auto begin = pls.begin() + pls.size() - out_of;
460  nth_element(begin, begin + set_size - 1, pls.end(), CmpMaxOrTerms());
461  shrink(pls.size() - out_of + set_size);
462 }
463 
465 OrContext::postlist(TermFreqs* termfreqs, bool bool_or)
466 {
467  if (!termfreqs_list.empty()) estimate_termfreqs(termfreqs);
468 
469  switch (pls.size()) {
470  case 0:
471  return {};
472  case 1: {
473  PostList* pl = pls[0];
474  pls.clear();
475  // If no_estimates was set then estimates will be empty.
476  return {pl, estimates.empty() ? nullptr : estimates.release_at(0)};
477  }
478  }
479 
480  unique_ptr<EstimateOp> est;
481  if (!qopt->get_no_estimates()) {
482  est.reset(new EstimateOp(EstimateOp::OR, first, last,
483  std::move(estimates)));
484  }
485 
486  if (bool_or) {
487  auto pl = new BoolOrPostList(pls.begin(), pls.end(), qopt->db_size);
488  // Empty pls so our destructor doesn't delete them all!
489  pls.clear();
490  return {pl, std::move(est)};
491  }
492 
493  // Make postlists into a heap so that the postlist with the greatest term
494  // frequency is at the top of the heap.
495  Heap::make(pls.begin(), pls.end(), ComparePostListTermFreqAscending());
496 
497  // Now build a tree of binary OrPostList objects.
498  //
499  // The algorithm used to build the tree is like that used to build an
500  // optimal Huffman coding tree. If we called next() repeatedly, this
501  // arrangement would minimise the number of method calls. Generally we
502  // don't actually do that, but this arrangement is still likely to be a
503  // good one, and it does minimise the work in the worst case.
504  while (true) {
505  // We build the tree such that at each branch:
506  //
507  // l.get_termfreq() >= r.get_termfreq()
508  //
509  // We do this so that the OrPostList class can be optimised assuming
510  // that this is the case.
511  PostList* r = pls.front();
512  Heap::pop(pls.begin(), pls.end(), ComparePostListTermFreqAscending());
513  pls.pop_back();
514  auto pl = new OrPostList(pls.front(), r, qopt->matcher);
515 
516  if (pls.size() == 1) {
517  pls.clear();
518  return {pl, std::move(est)};
519  }
520 
521  pls[0] = pl;
522  Heap::replace(pls.begin(), pls.end(),
524  }
525 }
526 
528 OrContext::postlist_max()
529 {
530  switch (pls.size()) {
531  case 0:
532  return {};
533  case 1: {
534  PostList* pl = pls[0];
535  pls.clear();
536  return {pl, estimates.release_at(0)};
537  }
538  }
539 
540  // Sort the postlists so that the postlist with the greatest term frequency
541  // is first.
542  sort(pls.begin(), pls.end(), ComparePostListTermFreqAscending());
543 
544  PostList* pl = new MaxPostList(pls.begin(), pls.end(),
545  qopt->matcher, qopt->db_size);
546  unique_ptr<EstimateOp> est;
547  if (!qopt->get_no_estimates()) {
548  // Same as OR for number of matches.
549  est.reset(new EstimateOp(EstimateOp::OR, first, last,
550  std::move(estimates)));
551  }
552  pls.clear();
553  return {pl, std::move(est)};
554 }
555 
556 class XorContext : public Context {
557  public:
558  XorContext(QueryOptimiser* qopt_, size_t reserve)
559  : Context(qopt_, reserve) { }
560 
561  PostListAndEstimate postlist(TermFreqs* termfreqs);
562 };
563 
565 XorContext::postlist(TermFreqs* termfreqs)
566 {
567  if (pls.empty())
568  return {};
569 
570  if (termfreqs) {
571  Assert(!termfreqs_list.empty());
572 
573  // We calculate the estimate assuming independence. The simplest
574  // way to calculate this seems to be a series of (n - 1) pairwise
575  // calculations, which gives the same answer regardless of the order.
576  auto& stats = *qopt->get_stats();
577  const TermFreqs& freqs = termfreqs_list[0];
578 
579  // Our caller should have ensured this.
580  Assert(stats.collection_size);
581  double scale = 1.0 / stats.collection_size;
582  double P_est = freqs.termfreq * scale;
583  double rtf_scale = 0.0;
584  if (stats.rset_size != 0) {
585  rtf_scale = 1.0 / stats.rset_size;
586  }
587  double Pr_est = freqs.reltermfreq * rtf_scale;
588  // If total_length is 0, cf must always be 0 so cf_scale is irrelevant.
589  double cf_scale = 0.0;
590  if (usual(stats.total_length != 0)) {
591  cf_scale = 1.0 / stats.total_length;
592  }
593  double Pc_est = freqs.collfreq * cf_scale;
594 
595  for (size_t i = 1; i < termfreqs_list.size(); ++i) {
596  const TermFreqs& f = termfreqs_list[i];
597  double P_i = f.termfreq * scale;
598  P_est += P_i - 2.0 * P_est * P_i;
599  double Pc_i = f.collfreq * cf_scale;
600  Pc_est += Pc_i - 2.0 * Pc_est * Pc_i;
601  // If the rset is empty, Pr_est should be 0 already, so leave
602  // it alone.
603  if (stats.rset_size != 0) {
604  double Pr_i = f.reltermfreq * rtf_scale;
605  Pr_est += Pr_i - 2.0 * Pr_est * Pr_i;
606  }
607  }
608 
609  *termfreqs =
610  TermFreqs(Xapian::doccount(P_est * stats.collection_size + 0.5),
611  Xapian::doccount(Pr_est * stats.rset_size + 0.5),
612  Xapian::termcount(Pc_est * stats.total_length + 0.5));
613  }
614 
615  unique_ptr<EstimateOp> est;
616  if (!qopt->get_no_estimates()) {
617  est.reset(new EstimateOp(EstimateOp::XOR, first, last,
618  std::move(estimates)));
619  }
620  auto pl = new XorPostList(pls.begin(), pls.end(), qopt->matcher,
621  qopt->db_size);
622  // Empty pls so our destructor doesn't delete them all!
623  pls.clear();
624  return {pl, std::move(est)};
625 }
626 
627 class PosFilter {
629 
631  size_t begin, end;
632 
634 
635  public:
636  PosFilter(Xapian::Query::op op__, size_t begin_, size_t end_,
637  Xapian::termcount window_)
638  : op_(op__), begin(begin_), end(end_), window(window_) { }
639 
641  EstimateOp* est,
642  const vector<PostList*>& pls,
643  PostListTree* pltree,
644  TermFreqs* termfreqs) const
645  try {
646  auto terms_begin = pls.begin() + begin;
647  auto terms_end = pls.begin() + end;
648 
649  if (op_ == Xapian::Query::OP_NEAR) {
650  if (termfreqs) *termfreqs /= 2;
651  if (est) {
652  est = new EstimateOp(EstimateOp::NEAR, est);
653  }
654  pl = new NearPostList(pl, est,
655  window, terms_begin, terms_end, pltree);
656  } else if (window != end - begin) {
658  if (termfreqs) *termfreqs /= 3;
659  if (est) {
660  est = new EstimateOp(EstimateOp::PHRASE, est);
661  }
662  pl = new PhrasePostList(pl, est,
663  window, terms_begin, terms_end, pltree);
664  } else {
666  if (termfreqs) *termfreqs /= 4;
667  if (est) {
668  est = new EstimateOp(EstimateOp::EXACT_PHRASE, est);
669  }
670  pl = new ExactPhrasePostList(pl, est,
671  terms_begin, terms_end, pltree);
672  }
673  return {pl, est};
674  } catch (...) {
675  delete pl;
676  delete est;
677  throw;
678  }
679 };
680 
681 class AndContext : public Context {
682  list<PosFilter> pos_filters;
683 
684  unique_ptr<OrContext> not_ctx;
685 
686  unique_ptr<OrContext> maybe_ctx;
687 
693  bool match_all = false;
694 
695  public:
696  AndContext(QueryOptimiser* qopt_, size_t reserve)
697  : Context(qopt_, reserve) {
698  first = 1;
699  last = Xapian::docid(-1);
700  }
701 
702  bool add_postlist(PostList* pl, unique_ptr<EstimateOp>&& estimate,
703  TermFreqs* termfreqs) {
704  add_termfreqs(termfreqs);
705  if (pl) {
706  if (pls.empty() && termfreqs_list.size() > 1) {
707  qopt->destroy_postlist(pl);
708  return true;
709  }
710  pls.emplace_back(pl);
711  estimates.push_back(estimate.release());
712  Xapian::docid pl_first = first, pl_last = last;
713  pl->get_docid_range(pl_first, pl_last);
714  first = std::max(first, pl_first);
715  last = std::min(last, pl_last);
716  if (first <= last) {
717  return true;
718  }
719  }
720  shrink(0);
721  match_all = false;
722  return termfreqs != NULL;
723  }
724 
726  return add_postlist(p.pl, std::move(p.est), termfreqs);
727  }
728 
729  void set_match_all() { match_all = true; }
730 
731  void add_pos_filter(Query::op op_,
732  size_t n_subqs,
733  Xapian::termcount window);
734 
735  OrContext& get_not_ctx(size_t reserve) {
736  if (!not_ctx) {
737  not_ctx.reset(new OrContext(qopt, reserve));
738  }
739  return *not_ctx;
740  }
741 
742  OrContext& get_maybe_ctx(size_t reserve) {
743  if (!maybe_ctx) {
744  maybe_ctx.reset(new OrContext(qopt, reserve));
745  }
746  return *maybe_ctx;
747  }
748 
749  PostListAndEstimate postlist(TermFreqs* termfreqs);
750 };
751 
752 void
753 AndContext::add_pos_filter(Query::op op_,
754  size_t n_subqs,
755  Xapian::termcount window)
756 {
757  Assert(n_subqs > 1);
758  size_t end = pls.size();
759  size_t begin = end - n_subqs;
760  pos_filters.push_back(PosFilter(op_, begin, end, window));
761 }
762 
763 template<typename T, typename U>
764 inline static T
765 estimate_and_not(T l, T r, U n)
766 {
767  // We calculate the estimates assuming independence. With this assumption,
768  // the estimate is the product of the estimates for the sub-postlists
769  // (with the right side this is inverted by subtracting from the total
770  // size), divided by the total size.
771  return static_cast<T>((l * double(n - r)) / n + 0.5);
772 }
773 
775 AndContext::postlist(TermFreqs* termfreqs)
776 {
777  auto matcher = qopt->matcher;
778  auto db_size = qopt->db_size;
779 
780  if (termfreqs) {
781  Assert(!termfreqs_list.empty());
782 
783  // We calculate the estimate assuming independence. With this
784  // assumption, the estimate is the product of the estimates for the
785  // sub-postlists divided by db_size (n - 1) times.
786  const TermFreqs& freqs = termfreqs_list[0];
787 
788  double freqest = double(freqs.termfreq);
789  double relfreqest = double(freqs.reltermfreq);
790  double collfreqest = double(freqs.collfreq);
791 
792  auto& stats = *qopt->get_stats();
793 
794  // Our caller should have ensured this.
795  Assert(stats.collection_size);
796 
797  for (size_t i = 1; i < termfreqs_list.size(); ++i) {
798  const TermFreqs& f = termfreqs_list[i];
799 
800  // If the collection is empty, freqest should be 0 already, so
801  // leave it alone.
802  freqest *= f.termfreq / stats.collection_size;
803  if (usual(stats.total_length != 0)) {
804  collfreqest *= f.collfreq / stats.total_length;
805  }
806 
807  // If the rset is empty, relfreqest should be 0 already, so leave
808  // it alone.
809  if (stats.rset_size != 0)
810  relfreqest *= f.reltermfreq / stats.rset_size;
811  }
812 
813  *termfreqs =
814  TermFreqs(static_cast<Xapian::doccount>(freqest + 0.5),
815  static_cast<Xapian::doccount>(relfreqest + 0.5),
816  static_cast<Xapian::termcount>(collfreqest + 0.5));
817  }
818 
819  unique_ptr<PostList> pl;
820  unique_ptr<EstimateOp> est;
821  switch (pls.size()) {
822  case 0: {
823  if (!match_all) {
824  // The "and" part doesn't match anything, so any "not" part or
825  // positional filters are irrelevant.
826  return {};
827  }
828  auto [new_pl, new_est] = qopt->open_post_list({}, 0, 0.0, nullptr);
829  pl.reset(new_pl);
830  est = std::move(new_est);
831  break;
832  }
833  case 1:
834  pl.reset(pls[0]);
835  est.reset(estimates.release_at(0));
836  break;
837  default:
838  pl.reset(new AndPostList(pls.begin(), pls.end(), matcher));
839  if (!qopt->get_no_estimates()) {
840  est.reset(new EstimateOp(EstimateOp::AND, first, last,
841  std::move(estimates)));
842  }
843  break;
844  }
845 
846  if (not_ctx && !not_ctx->empty()) {
847  if (not_ctx->get_last() < first || not_ctx->get_first() > last) {
848  // The ranges don't overlap so the right side has no effect.
849  // The call to not_ctx.reset() below will clean up the estimate
850  // stack.
851  } else {
852  TermFreqs r_freqs;
853  auto [rhs, rhs_est] = not_ctx->postlist(termfreqs ? &r_freqs : NULL,
854  true);
855  if (termfreqs) {
856  TermFreqs& freqs = *termfreqs;
857  auto& stats = *qopt->get_stats();
858 
859  // Our caller should have ensured this.
860  Assert(stats.collection_size);
861  freqs.termfreq = estimate_and_not(freqs.termfreq,
862  r_freqs.termfreq,
863  stats.collection_size);
864 
865  // If total_length is 0 then collfreq should always be 0 (since
866  // total_length is the sum of all collfreq values) so nothing
867  // to do.
868  if (stats.total_length != 0) {
869  freqs.collfreq = estimate_and_not(freqs.collfreq,
870  r_freqs.collfreq,
871  stats.total_length);
872  }
873 
874  // If the rset is empty then relfreqest should always be 0 so
875  // nothing to do.
876  if (stats.rset_size != 0) {
878  r_freqs.reltermfreq,
879  stats.rset_size);
880  }
881  }
882 
883  pl.reset(new AndNotPostList(pl.release(), rhs, db_size));
884  if (!qopt->get_no_estimates()) {
885  // The bounds are the same as those for the left side.
886  est.reset(new EstimateOp(EstimateOp::AND_NOT, first, last,
887  std::move(est), std::move(rhs_est)));
888  }
889  }
890  not_ctx.reset();
891  }
892 
893  // Sort the positional filters to try to apply them in an efficient order.
894  // FIXME: We need to figure out what that is! Try applying lowest cf/tf
895  // first?
896 
897  // Apply any positional filters.
898  for (const PosFilter& filter : pos_filters) {
899  auto [new_pl, new_est] = filter.postlist(pl.release(), est.release(),
900  pls, matcher, termfreqs);
901  pl.reset(new_pl);
902  est = std::move(new_est);
903  }
904 
905  // Empty pls so our destructor doesn't delete them all!
906  pls.clear();
907 
908  if (maybe_ctx && !maybe_ctx->empty()) {
909  if (maybe_ctx->get_last() < first || maybe_ctx->get_first() > last) {
910  // The ranges don't overlap so the right side has no effect.
911  // The call to maybe_ctx.reset() below will clean up the estimate
912  // stack.
913  } else {
914  // For OP_AND_MAYBE only the LHS determines which documents match
915  // (the RHS only adds weight) so the estimate is just that for the
916  // LHS. It would be useless extra work to generate estimates for
917  // anything on the RHS, and create a problem with the lifetime of
918  // the EstimateOp object which would need to live until after the
919  // match for any PostList that can report stats.
920  bool save_no_estimates = qopt->get_no_estimates();
921  qopt->set_no_estimates(true);
922  auto [rhs, rhs_est] = maybe_ctx->postlist(termfreqs);
923  qopt->set_no_estimates(save_no_estimates);
924 
925  // If this assertion fails, there's probably a missing check for
926  // qopt->get_no_estimates() somewhere.
927  Assert(!rhs_est);
928 
929  // A NULL PostList from OrContext::postlist() can only mean that
930  // maybe_ctx is empty, but in that case get_last() returns zero
931  // which means we would have taken the branch above.
932  Assert(rhs);
933 
934  pl.reset(new AndMaybePostList(pl.release(), rhs, matcher));
935  }
936  maybe_ctx.reset();
937  }
938 
939  return {pl.release(), est.release()};
940 }
941 
942 }
943 
944 Query::Internal::~Internal() { }
945 
946 size_t
947 Query::Internal::get_num_subqueries() const noexcept
948 {
949  return 0;
950 }
951 
952 const Query
953 Query::Internal::get_subquery(size_t) const
954 {
955  throw Xapian::InvalidArgumentError("get_subquery() not meaningful for this Query object");
956 }
957 
959 Query::Internal::get_wqf() const
960 {
961  throw Xapian::InvalidArgumentError("get_wqf() not meaningful for this Query object");
962 }
963 
965 Query::Internal::get_pos() const
966 {
967  throw Xapian::InvalidArgumentError("get_pos() not meaningful for this Query object");
968 }
969 
970 void
971 Query::Internal::gather_terms(void *) const
972 {
973 }
974 
976 Query::Internal::get_length() const noexcept
977 {
978  return 0;
979 }
980 
982 Query::Internal::unserialise(const char ** p, const char * end,
983  const Registry & reg)
984 {
985  if (*p == end)
986  return NULL;
987  unsigned char ch = *(*p)++;
988  switch (ch >> 5) {
989  case 4: case 5: case 6: case 7: {
990  // Multi-way branch
991  //
992  // 1ccccnnn where:
993  // nnn -> n_subqs (0 means encoded value follows)
994  // cccc -> code (which OP_XXX)
995  size_t n_subqs = ch & 0x07;
996  if (n_subqs == 0) {
997  if (!unpack_uint(p, end, &n_subqs)) {
999  }
1000  n_subqs += 8;
1001  }
1002  unsigned char code = (ch >> 3) & 0x0f;
1003  Xapian::termcount parameter = 0;
1004  if (code >= 13) {
1005  if (!unpack_uint(p, end, &parameter)) {
1007  }
1008  }
1010  switch (code) {
1011  case 0: // OP_AND
1012  result = new Xapian::Internal::QueryAnd(n_subqs);
1013  break;
1014  case 1: // OP_OR
1015  result = new Xapian::Internal::QueryOr(n_subqs);
1016  break;
1017  case 2: // OP_AND_NOT
1018  result = new Xapian::Internal::QueryAndNot(n_subqs);
1019  break;
1020  case 3: // OP_XOR
1021  result = new Xapian::Internal::QueryXor(n_subqs);
1022  break;
1023  case 4: // OP_AND_MAYBE
1024  result = new Xapian::Internal::QueryAndMaybe(n_subqs);
1025  break;
1026  case 5: // OP_FILTER
1027  result = new Xapian::Internal::QueryFilter(n_subqs);
1028  break;
1029  case 6: // OP_SYNONYM
1030  result = new Xapian::Internal::QuerySynonym(n_subqs);
1031  break;
1032  case 7: // OP_MAX
1033  result = new Xapian::Internal::QueryMax(n_subqs);
1034  break;
1035  case 13: // OP_ELITE_SET
1036  result = new Xapian::Internal::QueryEliteSet(n_subqs,
1037  parameter);
1038  break;
1039  case 14: // OP_NEAR
1040  result = new Xapian::Internal::QueryNear(n_subqs,
1041  parameter);
1042  break;
1043  case 15: // OP_PHRASE
1044  result = new Xapian::Internal::QueryPhrase(n_subqs,
1045  parameter);
1046  break;
1047  default:
1048  // 8 to 12 are currently unused.
1049  throw SerialisationError("Unknown multi-way branch Query operator");
1050  }
1051  do {
1052  result->add_subquery(Xapian::Query(unserialise(p, end, reg)));
1053  } while (--n_subqs);
1054  result->done();
1055  return result;
1056  }
1057  case 2: case 3: { // Term
1058  // Term
1059  //
1060  // 01ccLLLL where:
1061  // LLLL -> length (0 means encoded value follows)
1062  // cc -> code:
1063  // 0: wqf = 0; pos = 0
1064  // 1: wqf = 1; pos = 0
1065  // 2: wqf = 1; pos -> encoded value follows
1066  // 3: wqf -> encoded value follows; pos -> encoded value follows
1067  size_t len = ch & 0x0f;
1068  if (len == 0) {
1069  if (!unpack_uint(p, end, &len)) {
1071  }
1072  len += 16;
1073  }
1074  if (size_t(end - *p) < len)
1075  throw SerialisationError("Not enough data");
1076  string term(*p, len);
1077  *p += len;
1078 
1079  int code = ((ch >> 4) & 0x03);
1080 
1081  Xapian::termcount wqf = static_cast<Xapian::termcount>(code > 0);
1082  if (code == 3) {
1083  if (!unpack_uint(p, end, &wqf)) {
1085  }
1086  }
1087 
1088  Xapian::termpos pos = 0;
1089  if (code >= 2) {
1090  if (!unpack_uint(p, end, &pos)) {
1092  }
1093  }
1094 
1095  return new Xapian::Internal::QueryTerm(term, wqf, pos);
1096  }
1097  case 1: {
1098  // OP_VALUE_RANGE or OP_VALUE_GE or OP_VALUE_LE
1099  //
1100  // 001tssss where:
1101  // ssss -> slot number (15 means encoded value follows)
1102  // t -> op:
1103  // 0: OP_VALUE_RANGE (or OP_VALUE_LE if begin empty)
1104  // 1: OP_VALUE_GE
1105  Xapian::valueno slot = ch & 15;
1106  if (slot == 15) {
1107  if (!unpack_uint(p, end, &slot)) {
1109  }
1110  slot += 15;
1111  }
1112  string begin;
1113  if (!unpack_string(p, end, begin)) {
1115  }
1116  if (ch & 0x10) {
1117  // OP_VALUE_GE
1118  return new Xapian::Internal::QueryValueGE(slot, begin);
1119  }
1120 
1121  // OP_VALUE_RANGE
1122  string end_;
1123  if (!unpack_string(p, end, end_)) {
1125  }
1126  if (begin.empty()) // FIXME: is this right?
1127  return new Xapian::Internal::QueryValueLE(slot, end_);
1128  return new Xapian::Internal::QueryValueRange(slot, begin, end_);
1129  }
1130  case 0: {
1131  // Other operators
1132  //
1133  // 000ttttt where:
1134  // ttttt -> encodes which OP_XXX
1135  switch (ch & 0x1f) {
1136  case 0x00: // OP_INVALID
1137  return new Xapian::Internal::QueryInvalid();
1138  case 0x0a: { // Edit distance
1139  Xapian::termcount max_expansion;
1140  if (!unpack_uint(p, end, &max_expansion) || end - *p < 2) {
1141  throw SerialisationError("not enough data");
1142  }
1143  int flags = static_cast<unsigned char>(*(*p)++);
1144  op combiner = static_cast<op>(*(*p)++);
1145  unsigned edit_distance;
1146  size_t fixed_prefix_len;
1147  string pattern;
1148  if (!unpack_uint(p, end, &edit_distance) ||
1149  !unpack_uint(p, end, &fixed_prefix_len) ||
1150  !unpack_string(p, end, pattern)) {
1151  throw SerialisationError("not enough data");
1152  }
1154  return new QueryEditDistance(pattern,
1155  max_expansion,
1156  flags,
1157  combiner,
1158  edit_distance,
1159  fixed_prefix_len);
1160  }
1161  case 0x0b: { // Wildcard
1162  Xapian::termcount max_expansion;
1163  if (!unpack_uint(p, end, &max_expansion) || end - *p < 2) {
1164  throw SerialisationError("not enough data");
1165  }
1166  int flags = static_cast<unsigned char>(*(*p)++);
1167  op combiner = static_cast<op>(*(*p)++);
1168  string pattern;
1169  if (!unpack_string(p, end, pattern)) {
1170  throw SerialisationError("not enough data");
1171  }
1172  return new Xapian::Internal::QueryWildcard(pattern,
1173  max_expansion,
1174  flags,
1175  combiner);
1176  }
1177  case 0x0c: { // PostingSource
1178  string name;
1179  if (!unpack_string(p, end, name)) {
1180  throw SerialisationError("not enough data");
1181  }
1182 
1183  const PostingSource * reg_source = reg.get_posting_source(name);
1184  if (!reg_source) {
1185  string m = "PostingSource ";
1186  m += name;
1187  m += " not registered";
1188  throw SerialisationError(m);
1189  }
1190 
1191  string serialised_source;
1192  if (!unpack_string(p, end, serialised_source)) {
1193  throw SerialisationError("not enough data");
1194  }
1195  PostingSource* source =
1196  reg_source->unserialise_with_registry(serialised_source,
1197  reg);
1198  return new Xapian::Internal::QueryPostingSource(source->release());
1199  }
1200  case 0x0d: {
1202  double scale_factor = unserialise_double(p, end);
1203  return new QueryScaleWeight(scale_factor,
1204  Query(unserialise(p, end, reg)));
1205  }
1206  case 0x0e: {
1207  Xapian::termcount wqf;
1209  if (!unpack_uint(p, end, &wqf) ||
1210  !unpack_uint(p, end, &pos)) {
1211  throw SerialisationError("not enough data");
1212  }
1213  return new Xapian::Internal::QueryTerm({}, wqf, pos);
1214  }
1215  case 0x0f:
1216  return new Xapian::Internal::QueryTerm();
1217  default: // Others currently unused.
1218  break;
1219  }
1220  break;
1221  }
1222  }
1223  string msg = "Unknown Query serialisation: ";
1224  msg += str(ch);
1225  throw SerialisationError(msg);
1226 }
1227 
1228 bool
1229 Query::Internal::postlist_sub_and_like(AndContext& ctx,
1230  QueryOptimiser * qopt,
1231  double factor,
1232  TermFreqs* termfreqs) const
1233 {
1234  return ctx.add_postlist(postlist(qopt, factor, termfreqs), termfreqs);
1235 }
1236 
1237 void
1238 Query::Internal::postlist_sub_or_like(OrContext& ctx,
1239  QueryOptimiser* qopt,
1240  double factor,
1241  TermFreqs* termfreqs,
1242  bool keep_zero_weight) const
1243 {
1244  Xapian::termcount save_total_subqs = qopt->get_total_subqs();
1245  auto [pl_, est] = postlist(qopt, factor, termfreqs);
1246  unique_ptr<PostList> pl{pl_};
1247  if (!keep_zero_weight && pl && pl->recalc_maxweight() == 0.0) {
1248  // This subquery can't contribute any weight, so can be discarded.
1249  //
1250  // Restore the value of total_subqs so that percentages don't get
1251  // messed up if we increased total_subqs in the call to postlist()
1252  // above.
1253  qopt->set_total_subqs(save_total_subqs);
1254  qopt->destroy_postlist(pl.release());
1255  return;
1256  }
1257  ctx.add_postlist(pl.release(), est.release(), termfreqs);
1258 }
1259 
1260 void
1261 Query::Internal::postlist_sub_bool_or_like(OrContext& ctx,
1262  QueryOptimiser* qopt,
1263  TermFreqs* termfreqs) const
1264 {
1265  ctx.add_postlist(postlist(qopt, 0.0, termfreqs), termfreqs);
1266 }
1267 
1268 void
1269 Query::Internal::postlist_sub_xor(XorContext& ctx,
1270  QueryOptimiser* qopt,
1271  double factor,
1272  TermFreqs* termfreqs) const
1273 {
1274  ctx.add_postlist(postlist(qopt, factor, termfreqs), termfreqs);
1275 }
1276 
1277 namespace Internal {
1278 
1279 Query::op
1280 QueryTerm::get_type() const noexcept
1281 {
1282  return term.empty() ? Query::LEAF_MATCH_ALL : Query::LEAF_TERM;
1283 }
1284 
1285 string
1286 QueryTerm::get_description() const
1287 {
1288  string desc;
1289  if (term.empty()) {
1290  desc = "<alldocuments>";
1291  } else {
1292  description_append(desc, term);
1293  }
1294  if (wqf != 1) {
1295  desc += '#';
1296  desc += str(wqf);
1297  }
1298  if (pos) {
1299  desc += '@';
1300  desc += str(pos);
1301  }
1302  return desc;
1303 }
1304 
1305 QueryPostingSource::QueryPostingSource(PostingSource * source_)
1306  : source(source_)
1307 {
1308  if (!source_)
1309  throw Xapian::InvalidArgumentError("source parameter can't be NULL");
1310  if (source->_refs == 0) {
1311  // source_ isn't reference counted, so try to clone it. If clone()
1312  // isn't implemented, just use the object provided and it's the
1313  // caller's responsibility to ensure it stays valid while in use.
1314  PostingSource * cloned_source = source->clone();
1315  if (cloned_source) source = cloned_source->release();
1316  }
1317 }
1318 
1319 Query::op
1321 {
1323 }
1324 
1325 string
1327 {
1328  string desc = "PostingSource(";
1329  desc += source->get_description();
1330  desc += ')';
1331  return desc;
1332 }
1333 
1334 QueryScaleWeight::QueryScaleWeight(double factor, const Query & subquery_)
1335  : scale_factor(factor), subquery(subquery_)
1336 {
1337  if (rare(scale_factor < 0.0))
1338  throw Xapian::InvalidArgumentError("OP_SCALE_WEIGHT requires factor >= 0");
1339 }
1340 
1341 Query::op
1343 {
1344  return Query::OP_SCALE_WEIGHT;
1345 }
1346 
1347 size_t
1349 {
1350  return 1;
1351 }
1352 
1353 const Query
1355 {
1356  return subquery;
1357 }
1358 
1359 string
1361 {
1363  string desc = str(scale_factor);
1364  desc += " * ";
1365  desc += subquery.internal->get_description();
1366  return desc;
1367 }
1368 
1371  TermFreqs* termfreqs) const
1372 {
1373  LOGCALL(QUERY, PostListAndEstimate, "QueryTerm::postlist", qopt | factor | termfreqs);
1374  if (factor != 0.0)
1375  qopt->inc_total_subqs();
1376  RETURN(qopt->open_post_list(term, wqf, factor, termfreqs));
1377 }
1378 
1379 bool
1381  QueryOptimiser* qopt,
1382  double factor,
1383  TermFreqs* termfreqs) const
1384 {
1385  if (term.empty() && !qopt->need_positions && factor == 0.0 && !termfreqs) {
1386  // No-op MatchAll.
1387  ctx.set_match_all();
1388  return true;
1389  }
1390  return ctx.add_postlist(postlist(qopt, factor, termfreqs), termfreqs);
1391 }
1392 
1395  TermFreqs* termfreqs) const
1396 {
1397  LOGCALL(QUERY, PostListAndEstimate, "QueryPostingSource::postlist", qopt | factor | termfreqs);
1398  Assert(source);
1399  if (factor != 0.0)
1400  qopt->inc_total_subqs();
1401  unique_ptr<EstimateOp> est;
1402  if (!qopt->get_no_estimates()) {
1403  est.reset(new EstimateOp());
1404  }
1405  // Casting away const on the Database::Internal here is OK, as we wrap
1406  // them in a const Xapian::Database so non-const methods can't actually
1407  // be called on the Database::Internal object.
1408  const Xapian::Database wrappeddb(
1409  const_cast<Xapian::Database::Internal*>(&(qopt->db)));
1410  auto pl =
1411  new ExternalPostList(wrappeddb, source.get(), est.get(), factor,
1413  qopt->shard_index);
1414  if (termfreqs) {
1415  auto& stats = *qopt->get_stats();
1416  auto db_size = qopt->db_size;
1417  // Scale proportionately from this shard to the whole collection. Not
1418  // as good as summing over all shards, but that's harder to do.
1419  double termfreq = pl->get_termfreq();
1420  auto tf = termfreq * stats.collection_size / db_size;
1421  auto rtf = termfreq * stats.rset_size / db_size;
1422  auto cf = termfreq * stats.total_length / db_size;
1423  *termfreqs = TermFreqs(static_cast<Xapian::doccount>(tf + 0.5),
1424  static_cast<Xapian::doccount>(rtf + 0.5),
1425  static_cast<Xapian::termcount>(cf + 0.5));
1426  }
1427  RETURN({pl, std::move(est)});
1428 }
1429 
1432  TermFreqs* termfreqs) const
1433 {
1434  LOGCALL(QUERY, PostListAndEstimate, "QueryScaleWeight::postlist", qopt | factor | termfreqs);
1435  RETURN(subquery.internal->postlist(qopt, factor * scale_factor, termfreqs));
1436 }
1437 
1438 bool
1440  QueryOptimiser* qopt,
1441  double factor,
1442  TermFreqs* termfreqs) const
1443 {
1444  return subquery.internal->postlist_sub_and_like(ctx, qopt,
1445  factor * scale_factor,
1446  termfreqs);
1447 }
1448 
1449 void
1450 QueryTerm::gather_terms(void * void_terms) const
1451 {
1452  // Skip Xapian::Query::MatchAll (aka Xapian::Query("")).
1453  if (!term.empty()) {
1454  vector<pair<Xapian::termpos, string>> &terms =
1455  *static_cast<vector<pair<Xapian::termpos, string>>*>(void_terms);
1456  terms.push_back(make_pair(pos, term));
1457  }
1458 }
1459 
1460 static double
1461 string_frac(const string& s, size_t prefix)
1462 {
1463  double r = 0;
1464  double f = 1.0;
1465  for (size_t i = prefix; i != s.size(); ++i) {
1466  f /= 256.0;
1467  r += static_cast<unsigned char>(s[i]) * f;
1468  }
1469 
1470  return r;
1471 }
1472 
1473 static Xapian::doccount
1474 estimate_range_freq(const string& lo, const string& hi,
1475  const string& begin, const string* end,
1476  Xapian::doccount value_freq)
1477 {
1478  // Assume the values are evenly spread out between lo and hi.
1479  // FIXME: Perhaps we should store some sort of binned distribution?
1480  AssertRel(lo, <=, hi);
1481 
1482  size_t common_prefix_len = size_t(-1);
1483  do {
1484  UNSIGNED_OVERFLOW_OK(++common_prefix_len);
1485  // lo <= hi so while we're in the common prefix hi can't run out
1486  // before lo.
1487  if (common_prefix_len == lo.size()) {
1488  if (common_prefix_len != hi.size())
1489  break;
1490  // All values in the slot are the same. We should have optimised
1491  // to NULL if that singular value is outside the range, and if it's
1492  // inside the range then we know that the frequency is exactly the
1493  // value frequency.
1494  Assert(begin <= lo && (!end || hi <= *end));
1495  return value_freq;
1496  }
1497  AssertRel(common_prefix_len, !=, hi.size());
1498  } while (lo[common_prefix_len] == hi[common_prefix_len]);
1499 
1500  double l = string_frac(lo, common_prefix_len);
1501  double h = string_frac(hi, common_prefix_len);
1502  double denom = h - l;
1503  if (rare(denom == 0.0)) {
1504  // Weird corner case - hi != lo (because that's handled inside the loop
1505  // above) but they give the same string_frac value. Because we only
1506  // calculate the fraction starting from the first difference, this
1507  // should only happen if hi is lo + one or more trailing zero bytes.
1508 
1509  // The case where all set values lie within the range should be handled
1510  // at a higher level and we shouldn't get called.
1511  Assert(!(begin <= lo && (!end || hi <= *end)));
1512 
1513  // There must be partial overlap as the cases where the range
1514  // dominates the bounds and where the range is entirely outside the
1515  // bounds are both handled at a higher level.
1516  return value_freq / 2;
1517  }
1518 
1519  double b = l;
1520  if (begin > lo) {
1521  b = string_frac(begin, common_prefix_len);
1522  }
1523  double e = h;
1524  if (end && *end < hi) {
1525  // end is NULL for a ValueGePostList
1526  e = string_frac(*end, common_prefix_len);
1527  }
1528 
1529  double est = (e - b) / denom * value_freq;
1530  return Xapian::doccount(est + 0.5);
1531 }
1532 
1535  TermFreqs* termfreqs) const
1536 {
1537  LOGCALL(QUERY, PostListAndEstimate, "QueryValueRange::postlist", qopt | factor | termfreqs);
1538  if (factor != 0.0)
1539  qopt->inc_total_subqs();
1540  const Xapian::Database::Internal & db = qopt->db;
1541  const auto db_size = qopt->db_size;
1542  const string & lb = db.get_value_lower_bound(slot);
1543  if (lb.empty()) {
1544  // This should only happen if there are no values in this slot (which
1545  // could be because the backend just doesn't support values at all).
1546  // If there were values in the slot, the backend should have a
1547  // non-empty lower bound, even if it isn't a tight one.
1548  AssertEq(db.get_value_freq(slot), 0);
1549  if (termfreqs) *termfreqs = TermFreqs();
1550  RETURN({});
1551  }
1552  if (end < lb) {
1553  if (termfreqs) *termfreqs = TermFreqs();
1554  RETURN({});
1555  }
1556  const string & ub = db.get_value_upper_bound(slot);
1557  if (begin > ub) {
1558  if (termfreqs) *termfreqs = TermFreqs();
1559  RETURN({});
1560  }
1561 
1562  if (termfreqs) {
1563  // We scale these below.
1564  auto& stats = *qopt->get_stats();
1565  auto cf = clamp_cast<Xapian::termcount>(stats.total_length);
1566  *termfreqs = TermFreqs(stats.collection_size,
1567  stats.rset_size,
1568  cf);
1569  }
1570 
1571  auto value_freq = db.get_value_freq(slot);
1572  if (end >= ub) {
1573  if (begin <= lb) {
1574  // The known bounds for the slot both fall within the range so we
1575  // know the range matches whenever the value is set, which is
1576  // exactly value_freq times.
1577  unique_ptr<EstimateOp> est;
1578  if (!qopt->get_no_estimates())
1579  est.reset(new EstimateOp(value_freq));
1580  if (value_freq == db_size) {
1581  // This value is set for all documents in the current shard, so
1582  // we can replace it with a MatchAll postlist, which is
1583  // especially efficient if there are no gaps in the docids.
1584  RETURN({db.open_post_list({}), std::move(est)});
1585  }
1586  // We need to check which documents have a value set in this slot
1587  // but don't need to worry about the range bounds so we can use
1588  // ValueGePostList with an empty string as the lower bound which
1589  // means the range test just becomes a cheap `>= string()` test.
1590  if (termfreqs) *termfreqs *= double(value_freq) / db_size;
1591  auto pl = new ValueGePostList(&db, est.get(),
1592  value_freq, slot, string());
1593  RETURN({pl, std::move(est)});
1594  }
1595  auto tf_est = estimate_range_freq(lb, ub, begin, NULL, value_freq);
1596  unique_ptr<EstimateOp> est;
1597  if (!qopt->get_no_estimates())
1598  est.reset(new EstimateOp(Estimates{0, tf_est, value_freq}));
1599  if (termfreqs) *termfreqs *= double(tf_est) / db_size;
1600  auto pl = new ValueGePostList(&db, est.get(), tf_est, slot, begin);
1601  RETURN({pl, std::move(est)});
1602  }
1603  auto tf_est = estimate_range_freq(lb, ub, begin, &end, value_freq);
1604  unique_ptr<EstimateOp> est;
1605  if (!qopt->get_no_estimates())
1606  est.reset(new EstimateOp(Estimates{0, tf_est, value_freq}));
1607  if (termfreqs) *termfreqs *= double(tf_est) / db_size;
1608  auto pl = new ValueRangePostList(&db, est.get(), tf_est, slot, begin, end);
1609  RETURN({pl, std::move(est)});
1610 }
1611 
1612 void
1613 QueryValueRange::serialise(string & result) const
1614 {
1615  if (slot < 15) {
1616  result += static_cast<char>(0x20 | slot);
1617  } else {
1618  result += static_cast<char>(0x20 | 15);
1619  pack_uint(result, slot - 15);
1620  }
1621  pack_string(result, begin);
1622  pack_string(result, end);
1623 }
1624 
1625 Query::op
1627 {
1628  return Query::OP_VALUE_RANGE;
1629 }
1630 
1631 string
1633 {
1634  string desc = "VALUE_RANGE ";
1635  desc += str(slot);
1636  desc += ' ';
1637  description_append(desc, begin);
1638  desc += ' ';
1639  description_append(desc, end);
1640  return desc;
1641 }
1642 
1645  TermFreqs* termfreqs) const
1646 {
1647  LOGCALL(QUERY, PostListAndEstimate, "QueryValueLE::postlist", qopt | factor | termfreqs);
1648  if (factor != 0.0)
1649  qopt->inc_total_subqs();
1650  const Xapian::Database::Internal & db = qopt->db;
1651  const auto db_size = qopt->db_size;
1652  const string & lb = db.get_value_lower_bound(slot);
1653  if (lb.empty()) {
1654  // This should only happen if there are no values in this slot (which
1655  // could be because the backend just doesn't support values at all).
1656  // If there were values in the slot, the backend should have a
1657  // non-empty lower bound, even if it isn't a tight one.
1658  AssertEq(db.get_value_freq(slot), 0);
1659  if (termfreqs) *termfreqs = TermFreqs();
1660  RETURN({});
1661  }
1662  if (limit < lb) {
1663  if (termfreqs) *termfreqs = TermFreqs();
1664  RETURN({});
1665  }
1666 
1667  if (termfreqs) {
1668  // We scale these below.
1669  auto& stats = *qopt->get_stats();
1670  auto cf = clamp_cast<Xapian::termcount>(stats.total_length);
1671  *termfreqs = TermFreqs(stats.collection_size,
1672  stats.rset_size,
1673  cf);
1674  }
1675 
1676  auto value_freq = db.get_value_freq(slot);
1677  const string& ub = db.get_value_upper_bound(slot);
1678  if (limit >= ub) {
1679  // The known bounds for the slot both fall within the range so we
1680  // know the range matches whenever the value is set, which is
1681  // exactly value_freq times.
1682  unique_ptr<EstimateOp> est;
1683  if (!qopt->get_no_estimates())
1684  est.reset(new EstimateOp(value_freq));
1685  if (value_freq == db_size) {
1686  // This value is set for all documents in the current shard, so
1687  // we can replace it with a MatchAll postlist, which is
1688  // especially efficient if there are no gaps in the docids.
1689  RETURN({db.open_post_list({}), std::move(est)});
1690  }
1691  // We need to check which documents have a value set in this slot
1692  // but don't need to worry about the range bounds so we can use
1693  // ValueGePostList with an empty string as the lower bound which
1694  // means the range test just becomes a cheap `>= string()` test.
1695  if (termfreqs) *termfreqs *= double(value_freq) / db_size;
1696  auto pl = new ValueGePostList(&db, est.get(),
1697  value_freq, slot, string());
1698  RETURN({pl, std::move(est)});
1699  }
1700  auto tf_est = estimate_range_freq(lb, ub, string(), &limit, value_freq);
1701  unique_ptr<EstimateOp> est;
1702  if (!qopt->get_no_estimates())
1703  est.reset(new EstimateOp(Estimates{0, tf_est, value_freq}));
1704  if (termfreqs) *termfreqs *= double(tf_est) / db_size;
1705  auto pl = new ValueRangePostList(&db, est.get(),
1706  tf_est, slot, string(), limit);
1707  RETURN({pl, std::move(est)});
1708 }
1709 
1710 void
1711 QueryValueLE::serialise(string & result) const
1712 {
1713  // Encode as a range with an empty start (which only takes a single byte to
1714  // encode).
1715  if (slot < 15) {
1716  result += static_cast<char>(0x20 | slot);
1717  } else {
1718  result += static_cast<char>(0x20 | 15);
1719  pack_uint(result, slot - 15);
1720  }
1721  pack_string_empty(result);
1722  pack_string(result, limit);
1723 }
1724 
1725 Query::op
1726 QueryValueLE::get_type() const noexcept
1727 {
1728  return Query::OP_VALUE_LE;
1729 }
1730 
1731 string
1733 {
1734  string desc = "VALUE_LE ";
1735  desc += str(slot);
1736  desc += ' ';
1737  description_append(desc, limit);
1738  return desc;
1739 }
1740 
1743  TermFreqs* termfreqs) const
1744 {
1745  LOGCALL(QUERY, PostListAndEstimate, "QueryValueGE::postlist", qopt | factor | termfreqs);
1746  if (factor != 0.0)
1747  qopt->inc_total_subqs();
1748  const Xapian::Database::Internal & db = qopt->db;
1749  const auto db_size = qopt->db_size;
1750  const string & lb = db.get_value_lower_bound(slot);
1751  if (lb.empty()) {
1752  // This should only happen if there are no values in this slot (which
1753  // could be because the backend just doesn't support values at all).
1754  // If there were values in the slot, the backend should have a
1755  // non-empty lower bound, even if it isn't a tight one.
1756  AssertEq(db.get_value_freq(slot), 0);
1757  if (termfreqs) *termfreqs = TermFreqs();
1758  RETURN({});
1759  }
1760  const string& ub = db.get_value_upper_bound(slot);
1761  if (limit > ub) {
1762  if (termfreqs) *termfreqs = TermFreqs();
1763  RETURN({});
1764  }
1765 
1766  if (termfreqs) {
1767  // We scale these below.
1768  auto& stats = *qopt->get_stats();
1769  auto cf = clamp_cast<Xapian::termcount>(stats.total_length);
1770  *termfreqs = TermFreqs(stats.collection_size,
1771  stats.rset_size,
1772  cf);
1773  }
1774 
1775  auto value_freq = db.get_value_freq(slot);
1776  if (limit <= lb) {
1777  // The known bounds for the slot both fall within the range so we
1778  // know the range matches whenever the value is set, which is
1779  // exactly value_freq times.
1780  unique_ptr<EstimateOp> est;
1781  if (!qopt->get_no_estimates())
1782  est.reset(new EstimateOp(value_freq));
1783  if (value_freq == db_size) {
1784  // This value is set for all documents in the current shard, so
1785  // we can replace it with a MatchAll postlist, which is
1786  // especially efficient if there are no gaps in the docids.
1787  RETURN({db.open_post_list({}), std::move(est)});
1788  }
1789  // We need to check which documents have a value set in this slot
1790  // but don't need to worry about the range bounds so we can use
1791  // ValueGePostList with an empty string as the lower bound which
1792  // means the range test just becomes a cheap `>= string()` test.
1793  auto pl = new ValueGePostList(&db, est.get(),
1794  value_freq, slot, string());
1795  RETURN({pl, std::move(est)});
1796  }
1797  auto tf_est = estimate_range_freq(lb, ub, limit, NULL, value_freq);
1798  unique_ptr<EstimateOp> est;
1799  if (!qopt->get_no_estimates())
1800  est.reset(new EstimateOp(Estimates{0, tf_est, value_freq}));
1801  if (termfreqs) *termfreqs *= double(tf_est) / db_size;
1802  auto pl = new ValueGePostList(&db, est.get(), tf_est, slot, limit);
1803  RETURN({pl, std::move(est)});
1804 }
1805 
1806 void
1807 QueryValueGE::serialise(string & result) const
1808 {
1809  if (slot < 15) {
1810  result += static_cast<char>(0x20 | 0x10 | slot);
1811  } else {
1812  result += static_cast<char>(0x20 | 0x10 | 15);
1813  pack_uint(result, slot - 15);
1814  }
1815  pack_string(result, limit);
1816 }
1817 
1818 Query::op
1819 QueryValueGE::get_type() const noexcept
1820 {
1821  return Query::OP_VALUE_GE;
1822 }
1823 
1824 string
1826 {
1827  string desc = "VALUE_GE ";
1828  desc += str(slot);
1829  desc += ' ';
1830  description_append(desc, limit);
1831  return desc;
1832 }
1833 
1834 QueryWildcard::QueryWildcard(std::string_view pattern_,
1835  Xapian::termcount max_expansion_,
1836  int flags_,
1837  Query::op combiner_)
1838  : pattern(pattern_),
1839  max_expansion(max_expansion_),
1840  flags(flags_),
1841  combiner(combiner_)
1842 {
1843  if ((flags & ~Query::WILDCARD_LIMIT_MASK_) == 0) {
1844  head = min_len = pattern.size();
1845  max_len = numeric_limits<decltype(max_len)>::max();
1846  prefix = pattern;
1847  return;
1848  }
1849 
1850  size_t i = 0;
1851  while (i != pattern.size()) {
1852  // Check for characters with special meaning.
1853  switch (pattern[i]) {
1854  case '*':
1856  goto found_special;
1857  break;
1858  case '?':
1860  goto found_special;
1861  break;
1862  }
1863  prefix += pattern[i];
1864  ++i;
1865  head = i;
1866  }
1867 found_special:
1868 
1869  min_len = max_len = prefix.size();
1870 
1871  tail = i;
1872  size_t qm_count = 0;
1873  bool had_star = false;
1874  while (i != pattern.size()) {
1875  switch (pattern[i]) {
1876  default:
1877 default_case:
1878  suffix += pattern[i];
1879  ++min_len;
1880  ++max_len;
1881  break;
1882 
1883  case '*':
1885  goto default_case;
1886  // Matches zero or more characters.
1887  had_star = true;
1888  tail = i + 1;
1889  if (!suffix.empty()) {
1890  min_check_len = 0;
1891  suffix.clear();
1892  }
1893  break;
1894 
1895  case '?':
1897  goto default_case;
1898  // Matches exactly one character.
1899  tail = i + 1;
1900  if (!suffix.empty()) {
1901  min_check_len = 0;
1902  suffix.clear();
1903  }
1904  ++qm_count;
1905  ++min_len;
1907  break;
1908  }
1909 
1910  ++i;
1911  }
1912 
1913  if (had_star) {
1914  max_len = numeric_limits<decltype(max_len)>::max();
1915  } else if (qm_count > 1) {
1916  // `?` matches one Unicode character, which is 1-4 bytes in UTF-8, so
1917  // we have to actually check the pattern if there's more than one `?`
1918  // in it.
1919  min_check_len = 0;
1920  } else if (qm_count == 1) {
1921  // If the pattern contains exactly one `?` wildcard we need to check it
1922  // unless the candidate is exactly min_len bytes long. Note that we
1923  // know it can't match if it's < min_len long.
1924  min_check_len = min_len + 1;
1925  }
1926 }
1927 
1928 bool
1929 QueryWildcard::test_wildcard_(const string& candidate, size_t o, size_t p,
1930  size_t i) const
1931 {
1932  // FIXME: Optimisation potential here. We could compile the pattern to a
1933  // regex, or other tricks like calculating the min length needed after each
1934  // position that we test with this method - e.g. for foo*bar*x*baz there
1935  // must be at least 7 bytes after a position or there's no point testing if
1936  // "bar" matches there.
1937  for ( ; i != tail; ++i) {
1938  if ((flags & Query::WILDCARD_PATTERN_MULTI) && pattern[i] == '*') {
1939  if (++i == tail) {
1940  // '*' at end of variable part is easy!
1941  return true;
1942  }
1943  for (size_t test_o = o; test_o <= p; ++test_o) {
1944  if (test_wildcard_(candidate, test_o, p, i))
1945  return true;
1946  }
1947  return false;
1948  }
1949  if (o == p) return false;
1950  if ((flags & Query::WILDCARD_PATTERN_SINGLE) && pattern[i] == '?') {
1951  unsigned char b = candidate[o];
1952  if (b < 0xc0) {
1953  ++o;
1954  continue;
1955  }
1956  unsigned seqlen;
1957  if (b < 0xe0) {
1958  seqlen = 2;
1959  } else if (b < 0xf0) {
1960  seqlen = 3;
1961  } else {
1962  seqlen = 4;
1963  }
1964  if (rare(p - o < seqlen)) return false;
1965  o += seqlen;
1966  continue;
1967  }
1968 
1969  if (pattern[i] != candidate[o]) return false;
1970  ++o;
1971  }
1972  return (o == p);
1973 }
1974 
1975 bool
1976 QueryWildcard::test_prefix_known(const string& candidate) const
1977 {
1978  if (candidate.size() < min_len) return false;
1979  if (candidate.size() > max_len) return false;
1980  if (!endswith(candidate, suffix)) return false;
1981 
1982  if (candidate.size() < min_check_len) return true;
1983 
1984  return test_wildcard_(candidate, prefix.size(),
1985  candidate.size() - suffix.size(),
1986  head);
1987 }
1988 
1991  TermFreqs* termfreqs) const
1992 {
1993  LOGCALL(QUERY, PostListAndEstimate, "QueryWildcard::postlist", qopt | factor | termfreqs);
1994  OrContext ctx(qopt, 0);
1995  Query::op op = combiner;
1996  if (factor == 0.0 || op == Query::OP_SYNONYM) {
1997  if (factor == 0.0) {
1998  // If we have a factor of 0, we don't care about the weights, so
1999  // we're just like a normal OR query.
2000  op = Query::OP_OR;
2001  }
2002 
2003  bool old_compound_weight = qopt->compound_weight;
2004  if (!old_compound_weight) {
2005  qopt->compound_weight = (op == Query::OP_SYNONYM);
2006  }
2007 
2008  TermFreqs synonym_freqs;
2009  if (op == Query::OP_SYNONYM) {
2010  qopt->inc_total_subqs();
2011  ctx.expand_wildcard(this, 0.0, &synonym_freqs);
2012  } else {
2013  ctx.expand_wildcard(this, 0.0, termfreqs);
2014  }
2015 
2016  qopt->compound_weight = old_compound_weight;
2017 
2018  if (ctx.empty())
2019  RETURN({});
2020 
2021  if (op != Query::OP_SYNONYM)
2022  RETURN(ctx.postlist(termfreqs, true));
2023 
2024  // We build an OP_OR tree for OP_SYNONYM and then wrap it in a
2025  // SynonymPostList, which supplies the weights.
2026  RETURN(qopt->make_synonym_postlist(ctx.postlist(&synonym_freqs, true),
2027  factor, synonym_freqs));
2028  }
2029 
2030  ctx.expand_wildcard(this, factor, termfreqs);
2031 
2032  qopt->set_total_subqs(qopt->get_total_subqs() + ctx.size());
2033 
2034  if (ctx.empty())
2035  RETURN({});
2036 
2037  if (op == Query::OP_MAX)
2038  RETURN(ctx.postlist_max());
2039 
2040  RETURN(ctx.postlist(termfreqs));
2041 }
2042 
2043 termcount
2045 {
2046  // We currently assume wqf is 1 for calculating the synonym's weight
2047  // since conceptually the synonym is one "virtual" term. If we were
2048  // to combine multiple occurrences of the same synonym expansion into
2049  // a single instance with wqf set, we would want to track the wqf.
2050  return 1;
2051 }
2052 
2053 void
2054 QueryWildcard::serialise(string & result) const
2055 {
2056  result += static_cast<char>(0x0b);
2057  pack_uint(result, max_expansion);
2058  result += static_cast<unsigned char>(flags);
2059  result += static_cast<unsigned char>(combiner);
2060  pack_string(result, pattern);
2061 }
2062 
2063 Query::op
2064 QueryWildcard::get_type() const noexcept
2065 {
2066  return Query::OP_WILDCARD;
2067 }
2068 
2069 string
2071 {
2072  string desc = "WILDCARD ";
2073  switch (combiner) {
2074  case Query::OP_SYNONYM:
2075  desc += "SYNONYM ";
2076  break;
2077  case Query::OP_MAX:
2078  desc += "MAX ";
2079  break;
2080  case Query::OP_OR:
2081  desc += "OR ";
2082  break;
2083  default:
2084  desc += "BAD ";
2085  break;
2086  }
2087  description_append(desc, pattern);
2088  return desc;
2089 }
2090 
2091 int
2092 QueryEditDistance::test(const string& candidate) const
2093 {
2094  int threshold = get_threshold();
2095  int edist = edcalc(candidate, threshold);
2096  return edist <= threshold ? edist + 1 : 0;
2097 }
2098 
2101  TermFreqs* termfreqs) const
2102 {
2103  LOGCALL(QUERY, PostListAndEstimate, "QueryEditDistance::postlist", qopt | factor | termfreqs);
2104  OrContext ctx(qopt, 0);
2105  Query::op op = combiner;
2106  if (factor == 0.0 || op == Query::OP_SYNONYM) {
2107  if (factor == 0.0) {
2108  // If we have a factor of 0, we don't care about the weights, so
2109  // we're just like a normal OR query.
2110  op = Query::OP_OR;
2111  }
2112 
2113  bool old_compound_weight = qopt->compound_weight;
2114  if (!old_compound_weight) {
2115  qopt->compound_weight = (op == Query::OP_SYNONYM);
2116  }
2117 
2118  TermFreqs synonym_freqs;
2119  if (op == Query::OP_SYNONYM) {
2120  qopt->inc_total_subqs();
2121  ctx.expand_edit_distance(this, 0.0, &synonym_freqs);
2122  } else {
2123  ctx.expand_edit_distance(this, 0.0, termfreqs);
2124  }
2125 
2126  qopt->compound_weight = old_compound_weight;
2127 
2128  if (ctx.empty())
2129  RETURN({});
2130 
2131  if (op != Query::OP_SYNONYM)
2132  RETURN(ctx.postlist(termfreqs, true));
2133 
2134  // We build an OP_OR tree for OP_SYNONYM and then wrap it in a
2135  // SynonymPostList, which supplies the weights.
2136  RETURN(qopt->make_synonym_postlist(ctx.postlist(&synonym_freqs, true),
2137  factor, synonym_freqs));
2138  }
2139 
2140  ctx.expand_edit_distance(this, factor, termfreqs);
2141 
2142  qopt->set_total_subqs(qopt->get_total_subqs() + ctx.size());
2143 
2144  if (ctx.empty())
2145  RETURN({});
2146 
2147  if (op == Query::OP_MAX)
2148  RETURN(ctx.postlist_max());
2149 
2150  RETURN(ctx.postlist(termfreqs));
2151 }
2152 
2153 termcount
2155 {
2156  // We currently assume wqf is 1 for calculating the synonym's weight
2157  // since conceptually the synonym is one "virtual" term. If we were
2158  // to combine multiple occurrences of the same synonym expansion into
2159  // a single instance with wqf set, we would want to track the wqf.
2160  return 1;
2161 }
2162 
2163 void
2164 QueryEditDistance::serialise(string & result) const
2165 {
2166  result += static_cast<char>(0x0a);
2167  pack_uint(result, max_expansion);
2168  result += static_cast<unsigned char>(flags);
2169  result += static_cast<unsigned char>(combiner);
2170  pack_uint(result, edit_distance);
2171  pack_uint(result, fixed_prefix_len);
2172  pack_string(result, pattern);
2173 }
2174 
2175 Query::op
2177 {
2178  return Query::OP_EDIT_DISTANCE;
2179 }
2180 
2181 string
2183 {
2184  string desc = "EDIT_DISTANCE ";
2185  switch (combiner) {
2186  case Query::OP_SYNONYM:
2187  desc += "SYNONYM ";
2188  break;
2189  case Query::OP_MAX:
2190  desc += "MAX ";
2191  break;
2192  case Query::OP_OR:
2193  desc += "OR ";
2194  break;
2195  default:
2196  desc += "BAD ";
2197  break;
2198  }
2199  description_append(desc, pattern);
2200  desc += '~';
2201  desc += str(edit_distance);
2202  if (fixed_prefix_len) {
2203  desc += " fixed_prefix_len=";
2204  desc += str(fixed_prefix_len);
2205  }
2206  return desc;
2207 }
2208 
2210 QueryBranch::get_length() const noexcept
2211 {
2212  // Sum results from all subqueries.
2213  Xapian::termcount result = 0;
2215  for (i = subqueries.begin(); i != subqueries.end(); ++i) {
2216  // MatchNothing subqueries should have been removed by done(), but we
2217  // can't use Assert in a noexcept function. But we'll get a
2218  // segfault anyway.
2219  result += (*i).internal->get_length();
2220  }
2221  return result;
2222 }
2223 
2224 #define MULTIWAY(X) static_cast<unsigned char>(0x80 | (X) << 3)
2225 #define MISC(X) static_cast<unsigned char>(X)
2226 void
2227 QueryBranch::serialise_(string & result, Xapian::termcount parameter) const
2228 {
2229  static const unsigned char first_byte[] = {
2230  MULTIWAY(0), // OP_AND
2231  MULTIWAY(1), // OP_OR
2232  MULTIWAY(2), // OP_AND_NOT
2233  MULTIWAY(3), // OP_XOR
2234  MULTIWAY(4), // OP_AND_MAYBE
2235  MULTIWAY(5), // OP_FILTER
2236  MULTIWAY(14), // OP_NEAR
2237  MULTIWAY(15), // OP_PHRASE
2238  0, // OP_VALUE_RANGE
2239  MISC(3), // OP_SCALE_WEIGHT
2240  MULTIWAY(13), // OP_ELITE_SET
2241  0, // OP_VALUE_GE
2242  0, // OP_VALUE_LE
2243  MULTIWAY(6), // OP_SYNONYM
2244  MULTIWAY(7) // OP_MAX
2245  };
2246  Xapian::Query::op op_ = get_op();
2247  AssertRel(size_t(op_),<,sizeof(first_byte));
2248  unsigned char ch = first_byte[op_];
2249  if (ch & 0x80) {
2250  // Multi-way operator.
2251  if (subqueries.size() < 8)
2252  ch |= subqueries.size();
2253  result += ch;
2254  if (subqueries.size() >= 8)
2255  pack_uint(result, subqueries.size() - 8);
2256  if (ch >= MULTIWAY(13))
2257  pack_uint(result, parameter);
2258  } else {
2259  result += ch;
2260  }
2261 
2263  for (i = subqueries.begin(); i != subqueries.end(); ++i) {
2264  // MatchNothing subqueries should have been removed by done().
2265  Assert((*i).internal);
2266  (*i).internal->serialise(result);
2267  }
2268 
2269  // For OP_NEAR, OP_PHRASE, and OP_ELITE_SET, the window/set size gets
2270  // appended next by an overloaded serialise() method in the subclass.
2271 }
2272 
2273 void
2274 QueryBranch::serialise(string & result) const
2275 {
2276  QueryBranch::serialise_(result);
2277 }
2278 
2279 void
2280 QueryNear::serialise(string & result) const
2281 {
2282  // FIXME: window - subqueries.size() ?
2284 }
2285 
2286 void
2287 QueryPhrase::serialise(string & result) const
2288 {
2289  // FIXME: window - subqueries.size() ?
2291 }
2292 
2293 void
2294 QueryEliteSet::serialise(string & result) const
2295 {
2296  // FIXME: set_size - subqueries.size() ?
2298 }
2299 
2300 void
2301 QueryBranch::gather_terms(void * void_terms) const
2302 {
2303  // Gather results from all subqueries.
2305  for (i = subqueries.begin(); i != subqueries.end(); ++i) {
2306  // MatchNothing subqueries should have been removed by done().
2307  Assert((*i).internal);
2308  (*i).internal->gather_terms(void_terms);
2309  }
2310 }
2311 
2312 void
2314  QueryOptimiser* qopt,
2315  TermFreqs* termfreqs,
2316  size_t first) const
2317 {
2318  LOGCALL_VOID(MATCH, "QueryBranch::do_bool_or_like", ctx | qopt | termfreqs | first);
2319 
2320  // FIXME: we could optimise by merging OP_ELITE_SET and OP_OR like we do
2321  // for AND-like operations.
2322 
2323  // OP_SYNONYM with a single subquery is only simplified by
2324  // QuerySynonym::done() if the single subquery is a term or MatchAll.
2325  Assert(subqueries.size() >= 2 || get_op() == Query::OP_SYNONYM);
2326 
2328  for (q = subqueries.begin() + first; q != subqueries.end(); ++q) {
2329  // MatchNothing subqueries should have been removed by done().
2330  Assert((*q).internal);
2331  (*q).internal->postlist_sub_bool_or_like(ctx, qopt, termfreqs);
2332  }
2333 }
2334 
2335 void
2337  TermFreqs* termfreqs,
2338  Xapian::termcount elite_set_size, size_t first,
2339  bool keep_zero_weight) const
2340 {
2341  LOGCALL_VOID(MATCH, "QueryBranch::do_or_like", ctx | qopt | factor | termfreqs | elite_set_size | first | keep_zero_weight);
2342 
2343  // FIXME: we could optimise by merging OP_ELITE_SET and OP_OR like we do
2344  // for AND-like operations.
2345 
2346  // OP_SYNONYM with a single subquery is only simplified by
2347  // QuerySynonym::done() if the single subquery is a term or MatchAll.
2348  Assert(subqueries.size() >= 2 || get_op() == Query::OP_SYNONYM);
2349 
2350  size_t size_before = ctx.size();
2352  for (q = subqueries.begin() + first; q != subqueries.end(); ++q) {
2353  // MatchNothing subqueries should have been removed by done().
2354  Assert((*q).internal);
2355  (*q).internal->postlist_sub_or_like(ctx, qopt, factor,
2356  termfreqs,
2357  keep_zero_weight);
2358  }
2359 
2360  size_t out_of = ctx.size() - size_before;
2361  if (elite_set_size && elite_set_size < out_of) {
2362  ctx.select_elite_set(elite_set_size, out_of);
2363  // FIXME: This isn't quite right as we flatten ORs under the ELITE_SET
2364  // and then pick from amongst all the subqueries. Consider:
2365  //
2366  // Query subqs[] = {q1 | q2, q3 | q4};
2367  // Query q(OP_ELITE_SET, begin(subqs), end(subqs), 1);
2368  //
2369  // Here q should be either q1 | q2 or q3 | q4, but actually it'll be
2370  // just one of q1 or q2 or q3 or q4 (assuming those aren't themselves
2371  // OP_OR or OP_OR-like queries).
2372  }
2373 }
2374 
2377  double factor,
2378  TermFreqs* termfreqs) const
2379 {
2380  LOGCALL(MATCH, PostListAndEstimate, "QueryBranch::do_synonym", qopt | factor | termfreqs);
2381  OrContext ctx(qopt, subqueries.size());
2382  if (factor == 0.0) {
2383  // If we have a factor of 0, we don't care about the weights, so
2384  // we're just like a normal OR query. An OP_SYNONYM sets factor=0
2385  // for its subqueries so this handles an OP_SYNONYM below an
2386  // OP_SYNONYM.
2387  do_bool_or_like(ctx, qopt, termfreqs);
2388  return ctx.postlist(termfreqs, true);
2389  }
2390 
2391  bool old_compound_weight = qopt->compound_weight;
2392  Assert(!old_compound_weight);
2393  qopt->compound_weight = true;
2394  TermFreqs synonym_freqs;
2395  do_bool_or_like(ctx, qopt, &synonym_freqs);
2396  PostListAndEstimate plest = ctx.postlist(&synonym_freqs, true);
2397  qopt->compound_weight = old_compound_weight;
2398  if (!plest.pl) return {};
2399 
2400  // We currently assume wqf is 1 for calculating the synonym's weight
2401  // since conceptually the synonym is one "virtual" term. If we were
2402  // to combine multiple occurrences of the same synonym expansion into
2403  // a single instance with wqf set, we would want to track the wqf.
2404 
2405  // We build an OP_OR tree for OP_SYNONYM and then wrap it in a
2406  // SynonymPostList, which supplies the weights.
2407  RETURN(qopt->make_synonym_postlist(std::move(plest), factor,
2408  synonym_freqs));
2409 }
2410 
2413  double factor,
2414  TermFreqs* termfreqs) const
2415 {
2416  LOGCALL(MATCH, PostListAndEstimate, "QueryBranch::do_max", qopt | factor | termfreqs);
2417  OrContext ctx(qopt, subqueries.size());
2418  if (factor == 0.0) {
2419  // Without the weights we're just like a normal OR query.
2420  do_bool_or_like(ctx, qopt, termfreqs);
2421  RETURN(ctx.postlist(termfreqs, true));
2422  }
2423 
2424  // If termfreqs is set that means we're below a synonym, but in that case
2425  // factor should be 0.0 which is handled above.
2426  Assert(!termfreqs);
2427 
2428  do_or_like(ctx, qopt, factor, termfreqs);
2429  // We currently assume wqf is 1 for calculating the OP_MAX's weight
2430  // since conceptually the OP_MAX is one "virtual" term. If we were
2431  // to combine multiple occurrences of the same OP_MAX expansion into
2432  // a single instance with wqf set, we would want to track the wqf.
2433  RETURN(ctx.postlist_max());
2434 }
2435 
2437 QueryBranch::get_type() const noexcept
2438 {
2439  return get_op();
2440 }
2441 
2442 size_t
2444 {
2445  return subqueries.size();
2446 }
2447 
2448 const Query
2450 {
2451  return subqueries[n];
2452 }
2453 
2454 const string
2456  Xapian::termcount parameter) const
2457 {
2458  string desc = "(";
2460  for (i = subqueries.begin(); i != subqueries.end(); ++i) {
2461  if (desc.size() > 1) {
2462  desc += op;
2463  if (parameter) {
2464  desc += str(parameter);
2465  desc += ' ';
2466  }
2467  }
2468  Assert((*i).internal);
2469  // MatchNothing subqueries should have been removed by done(), and we
2470  // shouldn't get called before done() is, since that happens at the
2471  // end of the Xapian::Query constructor.
2472  desc += (*i).internal->get_description();
2473  }
2474  desc += ')';
2475  return desc;
2476 }
2477 
2480 {
2481  // If window size not specified, default it.
2482  if (window == 0)
2484  return QueryAndLike::done();
2485 }
2486 
2487 void
2488 QueryScaleWeight::gather_terms(void * void_terms) const
2489 {
2490  subquery.internal->gather_terms(void_terms);
2491 }
2492 
2493 void QueryTerm::serialise(string & result) const
2494 {
2495  size_t len = term.size();
2496  if (len == 0) {
2497  if (wqf == 1 && pos == 0) {
2498  // Query::MatchAll
2499  result += '\x0f';
2500  } else {
2501  // Weird mutant versions of MatchAll
2502  result += '\x0e';
2503  pack_uint(result, wqf);
2504  pack_uint(result, pos);
2505  }
2506  } else if (wqf == 1) {
2507  if (pos == 0) {
2508  // Single occurrence free-text term without position set.
2509  if (len >= 16) {
2510  result += static_cast<char>(0x40 | 0x10);
2511  pack_uint(result, term.size() - 16);
2512  } else {
2513  result += static_cast<char>(0x40 | 0x10 | len);
2514  }
2515  result += term;
2516  } else {
2517  // Single occurrence free-text term with position set.
2518  if (len >= 16) {
2519  result += static_cast<char>(0x40 | 0x20);
2520  pack_uint(result, term.size() - 16);
2521  } else {
2522  result += static_cast<char>(0x40 | 0x20 | len);
2523  }
2524  result += term;
2525  pack_uint(result, pos);
2526  }
2527  } else if (wqf > 1 || pos > 0) {
2528  // General case.
2529  if (len >= 16) {
2530  result += static_cast<char>(0x40 | 0x30);
2531  pack_uint(result, term.size() - 16);
2532  } else if (len) {
2533  result += static_cast<char>(0x40 | 0x30 | len);
2534  }
2535  result += term;
2536  pack_uint(result, wqf);
2537  pack_uint(result, pos);
2538  } else {
2539  // Typical boolean term.
2540  AssertEq(wqf, 0);
2541  AssertEq(pos, 0);
2542  if (len >= 16) {
2543  result += static_cast<char>(0x40);
2544  pack_uint(result, term.size() - 16);
2545  } else {
2546  result += static_cast<char>(0x40 | len);
2547  }
2548  result += term;
2549  }
2550 }
2551 
2552 void QueryPostingSource::serialise(string & result) const
2553 {
2554  result += static_cast<char>(0x0c);
2555  pack_string(result, source->name());
2556  pack_string(result, source->serialise());
2557 }
2558 
2559 void QueryScaleWeight::serialise(string & result) const
2560 {
2562  result += '\x0d';
2563  result += serialise_double(scale_factor);
2564  subquery.internal->serialise(result);
2565 }
2566 
2567 void
2569 {
2570  // If the AndLike is already MatchNothing, do nothing.
2571  if (subqueries.size() == 1 && !subqueries[0].internal)
2572  return;
2573  // If we're adding MatchNothing, discard any previous subqueries.
2574  if (!subquery.internal)
2575  subqueries.clear();
2576  subqueries.push_back(subquery);
2577 }
2578 
2581 {
2582  // Empty AndLike gives MatchNothing.
2583  if (subqueries.empty())
2584  return NULL;
2585  // We handle any subquery being MatchNothing in add_subquery() by leaving
2586  // a single MatchNothing subquery, and so this check results in AndLike
2587  // giving MatchNothing.
2588  if (subqueries.size() == 1)
2589  return subqueries[0].internal.get();
2590  return this;
2591 }
2592 
2595  TermFreqs* termfreqs) const
2596 {
2597  LOGCALL(QUERY, PostListAndEstimate, "QueryAndLike::postlist", qopt | factor | termfreqs);
2598  AndContext ctx(qopt, subqueries.size());
2599  if (!postlist_sub_and_like(ctx, qopt, factor, termfreqs)) {
2600  RETURN({});
2601  }
2602  RETURN(ctx.postlist(termfreqs));
2603 }
2604 
2605 bool
2607  QueryOptimiser* qopt,
2608  double factor,
2609  TermFreqs* termfreqs) const
2610 {
2612  for (i = subqueries.begin(); i != subqueries.end(); ++i) {
2613  // MatchNothing subqueries should have been removed by done().
2614  Assert((*i).internal);
2615  if (!(*i).internal->postlist_sub_and_like(ctx, qopt, factor, termfreqs))
2616  return false;
2617  }
2618  return true;
2619 }
2620 
2621 void
2623 {
2624  // Drop any subqueries which are MatchNothing.
2625  if (subquery.internal)
2626  subqueries.push_back(subquery);
2627 }
2628 
2631 {
2632  // An empty OrLike gives MatchNothing. Note that add_subquery() drops any
2633  // subqueries which are MatchNothing.
2634  if (subqueries.empty())
2635  return NULL;
2636  if (subqueries.size() == 1)
2637  return subqueries[0].internal.get();
2638  return this;
2639 }
2640 
2641 void
2643 {
2644  if (!subqueries.empty()) {
2645  // We're adding the 2nd or subsequent subquery, so this subquery is
2646  // negated.
2647  if (!subqueries[0].internal) {
2648  // The left side is already MatchNothing so drop any right side.
2649  //
2650  // MatchNothing AND_NOT X == MatchNothing
2651  return;
2652  }
2653  if (!subquery.internal) {
2654  // Drop MatchNothing on the right of AndNot.
2655  //
2656  // X AND_NOT MatchNothing == X
2657  return;
2658  }
2659  if (subquery.get_type() == subquery.OP_SCALE_WEIGHT) {
2660  // Strip OP_SCALE_WEIGHT wrapping from queries on the right of
2661  // AndNot as no weight is taken from them.
2662  subqueries.push_back(subquery.get_subquery(0));
2663  // The Query constructor for OP_SCALE_WEIGHT constructor should
2664  // eliminate OP_SCALE_WEIGHT applied to MatchNothing.
2665  Assert(subquery.get_subquery(0).internal);
2666  return;
2667  }
2668  }
2669  subqueries.push_back(subquery);
2670 }
2671 
2674 {
2675  // Any MatchNothing right subqueries get discarded by add_subquery() - if
2676  // that leaves just the left subquery, return that.
2677  //
2678  // If left subquery is MatchNothing, then add_subquery() discards all right
2679  // subqueries, so this check also gives MatchNothing for this case.
2680  if (subqueries.size() == 1)
2681  return subqueries[0].internal.get();
2682  return this;
2683 }
2684 
2685 void
2687 {
2688  // If the left side of AndMaybe is already MatchNothing, do nothing.
2689  if (subqueries.size() == 1 && !subqueries[0].internal)
2690  return;
2691  // Drop any 2nd or subsequent subqueries which are MatchNothing.
2692  if (subquery.internal || subqueries.empty())
2693  subqueries.push_back(subquery);
2694 }
2695 
2698 {
2699  // Any MatchNothing right subqueries get discarded by add_subquery() - if
2700  // that leaves just the left subquery, return that.
2701  //
2702  // If left subquery is MatchNothing, then add_subquery() discards all right
2703  // subqueries, so this check also gives MatchNothing for this case.
2704  if (subqueries.size() == 1)
2705  return subqueries[0].internal.get();
2706  return this;
2707 }
2708 
2710 QueryOr::postlist(QueryOptimiser* qopt, double factor,
2711  TermFreqs* termfreqs) const
2712 {
2713  LOGCALL(QUERY, PostListAndEstimate, "QueryOr::postlist", qopt | factor | termfreqs);
2714  OrContext ctx(qopt, subqueries.size());
2715  if (factor == 0.0) {
2716  do_bool_or_like(ctx, qopt, termfreqs);
2717  RETURN(ctx.postlist(termfreqs, true));
2718  }
2719  do_or_like(ctx, qopt, factor, termfreqs);
2720  RETURN(ctx.postlist(termfreqs));
2721 }
2722 
2723 void
2725  double factor,
2726  TermFreqs* termfreqs,
2727  bool keep_zero_weight) const
2728 {
2729  do_or_like(ctx, qopt, factor, termfreqs, 0, 0, keep_zero_weight);
2730 }
2731 
2732 void
2734  QueryOptimiser* qopt,
2735  TermFreqs* termfreqs) const
2736 {
2737  do_bool_or_like(ctx, qopt, termfreqs);
2738 }
2739 
2742  TermFreqs* termfreqs) const
2743 {
2744  LOGCALL(QUERY, PostListAndEstimate, "QueryAndNot::postlist", qopt | factor | termfreqs);
2745  AndContext ctx(qopt, 1);
2746  if (!QueryAndNot::postlist_sub_and_like(ctx, qopt, factor, termfreqs)) {
2747  RETURN({});
2748  }
2749  RETURN(ctx.postlist(termfreqs));
2750 }
2751 
2752 bool
2754  QueryOptimiser* qopt,
2755  double factor,
2756  TermFreqs* termfreqs) const
2757 {
2758  // This invariant should be established by QueryAndNot::done() with
2759  // assistance from QueryAndNot::add_subquery().
2760  Assert(subqueries[0].internal);
2761  if (!subqueries[0].internal->postlist_sub_and_like(ctx, qopt, factor,
2762  termfreqs)) {
2763  return false;
2764  }
2765  do_bool_or_like(ctx.get_not_ctx(subqueries.size() - 1), qopt, termfreqs, 1);
2766  return true;
2767 }
2768 
2770 QueryXor::postlist(QueryOptimiser* qopt, double factor,
2771  TermFreqs* termfreqs) const
2772 {
2773  LOGCALL(QUERY, PostListAndEstimate, "QueryXor::postlist", qopt | factor | termfreqs);
2774  XorContext ctx(qopt, subqueries.size());
2775  postlist_sub_xor(ctx, qopt, factor, termfreqs);
2776  RETURN(ctx.postlist(termfreqs));
2777 }
2778 
2779 void
2781  QueryOptimiser* qopt,
2782  double factor,
2783  TermFreqs* termfreqs) const
2784 {
2786  for (i = subqueries.begin(); i != subqueries.end(); ++i) {
2787  // MatchNothing subqueries should have been removed by done().
2788  Assert((*i).internal);
2789  (*i).internal->postlist_sub_xor(ctx, qopt, factor, termfreqs);
2790  }
2791 }
2792 
2795  TermFreqs* termfreqs) const
2796 {
2797  LOGCALL(QUERY, PostListAndEstimate, "QueryAndMaybe::postlist", qopt | factor | termfreqs);
2798  AndContext ctx(qopt, 1);
2799  if (!QueryAndMaybe::postlist_sub_and_like(ctx, qopt, factor, termfreqs)) {
2800  RETURN({});
2801  }
2802  RETURN(ctx.postlist(termfreqs));
2803 }
2804 
2805 bool
2807  QueryOptimiser* qopt,
2808  double factor,
2809  TermFreqs* termfreqs) const
2810 {
2811  // This invariant should be established by QueryAndMaybe::done() with
2812  // assistance from QueryAndMaybe::add_subquery().
2813  Assert(subqueries[0].internal);
2814  if (!subqueries[0].internal->postlist_sub_and_like(ctx, qopt, factor,
2815  termfreqs)) {
2816  return false;
2817  }
2818  // We only need to consider the right branch or branches if we're weighted
2819  // - an unweighted OP_AND_MAYBE can be replaced with its left branch.
2820  if (factor != 0.0) {
2821  // Only keep zero-weight subqueries if we need their wdf because
2822  // they're underneath a compound weight.
2823  OrContext& maybe_ctx = ctx.get_maybe_ctx(subqueries.size() - 1);
2824  bool need_wdf = qopt->need_wdf_for_compound_weight();
2825  bool save_no_estimates = qopt->get_no_estimates();
2826  qopt->set_no_estimates(true);
2827  do_or_like(maybe_ctx, qopt, factor, termfreqs, 0, 1, need_wdf);
2828  qopt->set_no_estimates(save_no_estimates);
2829  }
2830  return true;
2831 }
2832 
2835  TermFreqs* termfreqs) const
2836 {
2837  LOGCALL(QUERY, PostListAndEstimate, "QueryFilter::postlist", qopt | factor | termfreqs);
2838  AndContext ctx(qopt, subqueries.size());
2839  if (!QueryFilter::postlist_sub_and_like(ctx, qopt, factor, termfreqs)) {
2840  RETURN({});
2841  }
2842  RETURN(ctx.postlist(termfreqs));
2843 }
2844 
2845 bool
2847  QueryOptimiser* qopt,
2848  double factor,
2849  TermFreqs* termfreqs) const
2850 {
2852  for (i = subqueries.begin(); i != subqueries.end(); ++i) {
2853  // MatchNothing subqueries should have been removed by done().
2854  Assert((*i).internal);
2855  if (!(*i).internal->postlist_sub_and_like(ctx, qopt, factor, termfreqs))
2856  return false;
2857  // Second and subsequent subqueries are unweighted.
2858  factor = 0.0;
2859  }
2860  return true;
2861 }
2862 
2863 bool
2865  AndContext& ctx,
2866  QueryOptimiser* qopt,
2867  double factor,
2868  TermFreqs* termfreqs) const
2869 {
2870  if (!qopt->db.has_positions()) {
2871  // No positions in this subdatabase so this matches nothing, which
2872  // means the whole andcontext matches nothing.
2873  //
2874  // Bailing out here means we don't recurse deeper and that means we
2875  // don't call QueryOptimiser::inc_total_subqs() for leaf postlists in
2876  // the phrase, but at least one shard will count them, and the matcher
2877  // takes the highest answer (since 1.4.6).
2878  ctx.shrink(0);
2879  return false;
2880  }
2881 
2882  bool old_need_positions = qopt->need_positions;
2883  qopt->need_positions = true;
2884 
2885  bool result = true;
2887  for (i = subqueries.begin(); i != subqueries.end(); ++i) {
2888  // MatchNothing subqueries should have been removed by done().
2889  Assert((*i).internal);
2890  PostListAndEstimate plest = (*i).internal->postlist(qopt, factor, NULL);
2891  if (plest.pl && (*i).internal->get_type() != Query::LEAF_TERM) {
2892  plest.pl = new OrPosPostList(plest.pl);
2893  }
2894  result = ctx.add_postlist(std::move(plest), termfreqs);
2895  if (!result) {
2896  if (factor == 0.0) break;
2897  // If we don't complete the iteration, the subquery count may be
2898  // wrong, and weighting information may not be filled in.
2899  while (i != subqueries.end()) {
2900  // MatchNothing subqueries should have been removed by done().
2901  // FIXME: Can we handle this more gracefully?
2902  Assert((*i).internal);
2903  qopt->destroy_postlist((*i).internal->postlist(qopt, factor,
2904  NULL).pl);
2905  ++i;
2906  }
2907  break;
2908  }
2909  }
2910  if (result) {
2911  // Record the positional filter to apply higher up the tree.
2912  ctx.add_pos_filter(op, subqueries.size(), window);
2913  }
2914 
2915  qopt->need_positions = old_need_positions;
2916  return result;
2917 }
2918 
2919 bool
2921  QueryOptimiser* qopt,
2922  double factor,
2923  TermFreqs* termfreqs) const
2924 {
2925  constexpr auto OP_PHRASE = Query::OP_PHRASE;
2926  return QueryWindowed::postlist_windowed(OP_PHRASE, ctx, qopt, factor,
2927  termfreqs);
2928 }
2929 
2930 bool
2932  QueryOptimiser* qopt,
2933  double factor,
2934  TermFreqs* termfreqs) const
2935 {
2936  constexpr auto OP_NEAR = Query::OP_NEAR;
2937  return QueryWindowed::postlist_windowed(OP_NEAR, ctx, qopt, factor,
2938  termfreqs);
2939 }
2940 
2943  TermFreqs* termfreqs) const
2944 {
2945  LOGCALL(QUERY, PostListAndEstimate, "QueryEliteSet::postlist", qopt | factor | termfreqs);
2946  OrContext ctx(qopt, subqueries.size());
2947  do_or_like(ctx, qopt, factor, termfreqs, set_size);
2948  RETURN(ctx.postlist(termfreqs));
2949 }
2950 
2951 void
2953  double factor,
2954  TermFreqs* termfreqs,
2955  bool keep_zero_weight) const
2956 {
2957  do_or_like(ctx, qopt, factor, termfreqs, set_size, 0, keep_zero_weight);
2958 }
2959 
2962  TermFreqs* termfreqs) const
2963 {
2964  LOGCALL(QUERY, PostListAndEstimate, "QuerySynonym::postlist", qopt | factor | termfreqs);
2965  // Save and restore total_subqs so we only add one for the whole
2966  // OP_SYNONYM subquery (or none if we're not weighted).
2967  Xapian::termcount save_total_subqs = qopt->get_total_subqs();
2968  if (factor != 0.0)
2969  ++save_total_subqs;
2970  PostListAndEstimate plest = do_synonym(qopt, factor, termfreqs);
2971  qopt->set_total_subqs(save_total_subqs);
2972  return plest;
2973 }
2974 
2977 {
2978  // An empty Synonym gives MatchNothing. Note that add_subquery() drops any
2979  // subqueries which are MatchNothing.
2980  if (subqueries.empty())
2981  return NULL;
2982  if (subqueries.size() == 1) {
2983  Query::op sub_type = subqueries[0].get_type();
2984  // Synonym of a single subquery should only be simplified if that
2985  // subquery is a term (or MatchAll), or if it's also OP_SYNONYM. Note
2986  // that MatchNothing subqueries are dropped, so we'd never get here
2987  // with a single MatchNothing subquery.
2988  if (sub_type == Query::LEAF_TERM || sub_type == Query::LEAF_MATCH_ALL ||
2989  sub_type == Query::OP_SYNONYM) {
2990  return subqueries[0].internal.get();
2991  }
2992  if (sub_type == Query::OP_WILDCARD) {
2993  auto q = static_cast<QueryWildcard*>(subqueries[0].internal.get());
2994  // SYNONYM over WILDCARD X -> WILDCARD SYNONYM for any combiner X.
2996  }
2997  if (sub_type == Query::OP_EDIT_DISTANCE) {
2998  auto q =
2999  static_cast<QueryEditDistance*>(subqueries[0].internal.get());
3000  // SYNONYM over EDIT_DISTANCE X -> EDIT_DISTANCE SYNONYM for any
3001  // combiner X.
3003  }
3004  }
3005  return this;
3006 }
3007 
3009 QueryMax::postlist(QueryOptimiser* qopt, double factor,
3010  TermFreqs* termfreqs) const
3011 {
3012  LOGCALL(QUERY, PostListAndEstimate, "QueryMax::postlist", qopt | factor | termfreqs);
3013  // Save and restore total_subqs so we only add one for the whole
3014  // OP_MAX subquery (or none if we're not weighted).
3015  Xapian::termcount save_total_subqs = qopt->get_total_subqs();
3016  if (factor != 0.0)
3017  ++save_total_subqs;
3018  PostListAndEstimate plest = do_max(qopt, factor, termfreqs);
3019  qopt->set_total_subqs(save_total_subqs);
3020  return plest;
3021 }
3022 
3025 {
3026  return Xapian::Query::OP_AND;
3027 }
3028 
3031 {
3032  return Xapian::Query::OP_OR;
3033 }
3034 
3037 {
3039 }
3040 
3043 {
3044  return Xapian::Query::OP_XOR;
3045 }
3046 
3049 {
3051 }
3052 
3055 {
3056  return Xapian::Query::OP_FILTER;
3057 }
3058 
3061 {
3062  return Xapian::Query::OP_NEAR;
3063 }
3064 
3067 {
3068  return Xapian::Query::OP_PHRASE;
3069 }
3070 
3073 {
3075 }
3076 
3079 {
3081 }
3082 
3085 {
3086  return Xapian::Query::OP_MAX;
3087 }
3088 
3089 string
3091 {
3092  return get_description_helper(" AND ");
3093 }
3094 
3095 string
3097 {
3098  return get_description_helper(" OR ");
3099 }
3100 
3101 string
3103 {
3104  return get_description_helper(" AND_NOT ");
3105 }
3106 
3107 string
3109 {
3110  return get_description_helper(" XOR ");
3111 }
3112 
3113 string
3115 {
3116  return get_description_helper(" AND_MAYBE ");
3117 }
3118 
3119 string
3121 {
3122  return get_description_helper(" FILTER ");
3123 }
3124 
3125 string
3127 {
3128  return get_description_helper(" NEAR ", window);
3129 }
3130 
3131 string
3133 {
3134  return get_description_helper(" PHRASE ", window);
3135 }
3136 
3137 string
3139 {
3140  return get_description_helper(" ELITE_SET ", set_size);
3141 }
3142 
3143 string
3145 {
3146  if (subqueries.size() == 1) {
3147  string d = "(SYNONYM ";
3148  d += subqueries[0].internal->get_description();
3149  d += ")";
3150  return d;
3151  }
3152  return get_description_helper(" SYNONYM ");
3153 }
3154 
3155 string
3157 {
3158  return get_description_helper(" MAX ");
3159 }
3160 
3162 QueryInvalid::get_type() const noexcept
3163 {
3165 }
3166 
3169 {
3170  throw Xapian::InvalidOperationError("Query is invalid");
3171 }
3172 
3173 void
3174 QueryInvalid::serialise(std::string & result) const
3175 {
3176  result += static_cast<char>(0x00);
3177 }
3178 
3179 string
3181 {
3182  return "<INVALID>";
3183 }
3184 
3185 }
3186 }
PostList class implementing Query::OP_AND_MAYBE.
PostList class implementing Query::OP_AND_NOT.
N-way AND postlist.
static Xapian::Query query(Xapian::Query::op op, const string &t1=string(), const string &t2=string(), const string &t3=string(), const string &t4=string(), const string &t5=string(), const string &t6=string(), const string &t7=string(), const string &t8=string(), const string &t9=string(), const string &t10=string())
Definition: api_anydb.cc:62
char name[9]
Definition: dbcheck.cc:57
PostList class implementing unweighted Query::OP_OR.
Cast a value to a type, clamping out of range values.
PostList class implementing Query::OP_AND_MAYBE.
PostList class implementing Query::OP_AND_NOT.
N-way AND postlist.
Definition: andpostlist.h:32
PostList class implementing unweighted Query::OP_OR.
Class for estimating the total number of matching documents.
Definition: estimateop.h:64
@ EXACT_PHRASE
Definition: estimateop.h:71
Postlist which matches an exact phrase using positional information.
Abstract base class for leaf postlists.
Definition: leafpostlist.h:40
N-way OR postlist with wt=max(wt_i).
Definition: maxpostlist.h:31
Postlist which matches terms occurring within a specified window.
Definition: nearpostlist.h:41
Wrapper postlist providing positions for an OR.
Definition: orpospostlist.h:28
PostList class implementing Query::OP_OR.
Definition: orpostlist.h:29
Postlist which matches a phrase using positional information.
bool * get_max_weight_cached_flag_ptr()
Return pointer to flag to set to false to invalidate cached max weight.
Definition: postlisttree.h:107
Virtual base class for Database internals.
virtual std::string get_value_upper_bound(valueno slot) const =0
Get an upper bound on the values stored in the given value slot.
virtual std::string get_value_lower_bound(valueno slot) const =0
Get a lower bound on the values stored in the given value slot.
virtual PostList * open_post_list(std::string_view term) const =0
Return a PostList suitable for use in a PostingIterator.
virtual bool has_positions() const =0
Check whether this database contains any positional information.
virtual doccount get_value_freq(valueno slot) const =0
Return the frequency of a given value slot.
An indexed database of documents.
Definition: database.h:75
PostListAndEstimate postlist(TermFreqs *termfreqs)
bool add_postlist(PostList *pl, unique_ptr< EstimateOp > &&estimate, TermFreqs *termfreqs)
OrContext & get_maybe_ctx(size_t reserve)
AndContext(QueryOptimiser *qopt_, size_t reserve)
unique_ptr< OrContext > maybe_ctx
void add_pos_filter(Query::op op_, size_t n_subqs, Xapian::termcount window)
OrContext & get_not_ctx(size_t reserve)
bool add_postlist(PostListAndEstimate p, TermFreqs *termfreqs)
list< PosFilter > pos_filters
unique_ptr< OrContext > not_ctx
VecUniquePtr< EstimateOp > estimates
void add_postlist(PostList *pl, EstimateOp *estimate, TermFreqs *termfreqs)
vector< PostList * > pls
vector< TermFreqs > termfreqs_list
Xapian::docid get_first() const
void expand_edit_distance(const QueryEditDistance *query, double factor, TermFreqs *termfreqs)
Expand an edit distance query.
Context(QueryOptimiser *qopt_, size_t reserve)
void add_termfreqs(TermFreqs *termfreqs)
void expand_wildcard(const QueryWildcard *query, double factor, TermFreqs *termfreqs)
Expand a wildcard query.
void add_postlist(PostListAndEstimate p, TermFreqs *termfreqs)
Xapian::docid get_last() const
Xapian::termcount size() const
void shrink(size_t new_size)
PostListAndEstimate postlist_max()
void select_elite_set(size_t set_size, size_t out_of)
Select the best set_size postlists from the last out_of added.
PostListAndEstimate postlist(TermFreqs *termfreqs, bool bool_or=false)
OrContext(QueryOptimiser *qopt_, size_t reserve)
size_t begin
Start and end indices for the PostLists this positional filter uses.
PostListAndEstimate postlist(PostList *pl, EstimateOp *est, const vector< PostList * > &pls, PostListTree *pltree, TermFreqs *termfreqs) const
PosFilter(Xapian::Query::op op__, size_t begin_, size_t end_, Xapian::termcount window_)
Abstract base class for postlists.
Definition: postlist.h:40
Xapian::doccount get_termfreq() const
Get an estimate of the number of documents this PostList will return.
Definition: postlist.h:67
virtual double recalc_maxweight()=0
Recalculate the upper bound on what get_weight() can return.
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
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
bool postlist_sub_and_like(AndContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
void add_subquery(const Xapian::Query &subquery)
bool postlist_sub_and_like(AndContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
void add_subquery(const Xapian::Query &subquery)
Xapian::Query::op get_op() const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
std::string get_description() const
Xapian::Query::op get_op() const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
std::string get_description() const
void add_subquery(const Xapian::Query &subquery)
bool postlist_sub_and_like(AndContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
Xapian::Query::op get_op() const
std::string get_description() const
virtual Query::Internal * done()=0
void do_bool_or_like(OrContext &ctx, QueryOptimiser *qopt, TermFreqs *termfreqs, size_t first=0) const
void do_or_like(OrContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs, Xapian::termcount elite_set_size=0, size_t first=0, bool keep_zero_weight=true) const
Process OR-like subqueries.
virtual Xapian::Query::op get_op() const =0
void serialise_(std::string &result, Xapian::termcount parameter=0) const
virtual void add_subquery(const Xapian::Query &subquery)=0
Xapian::Query::op get_type() const noexcept
const std::string get_description_helper(const char *op, Xapian::termcount window=0) const
PostListAndEstimate do_max(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
void gather_terms(void *void_terms) const
termcount get_length() const noexcept
PostListAndEstimate do_synonym(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
const Query get_subquery(size_t n) const
size_t get_num_subqueries() const noexcept
void serialise(std::string &result) const
void serialise(std::string &result) const
int test(const std::string &candidate) const
Perform edit distance test.
QueryEditDistance * change_combiner(Xapian::Query::op new_op)
Change the combining operator.
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
termcount get_length() const noexcept
Xapian::Query::op get_type() const noexcept
void serialise(std::string &result) const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
void postlist_sub_or_like(OrContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs, bool keep_zero_weight) const
std::string get_description() const
Xapian::Query::op get_op() const
std::string get_description() const
bool postlist_sub_and_like(AndContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
Xapian::Query::op get_op() const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
void serialise(std::string &result) const
Xapian::Query::op get_type() const noexcept
std::string get_description() const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
std::string get_description() const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
Xapian::Query::op get_op() const
void serialise(std::string &result) const
Xapian::Query::op get_op() const
std::string get_description() const
bool postlist_sub_and_like(AndContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
Xapian::termcount get_total_subqs() const
void destroy_postlist(PostList *pl)
PostListAndEstimate open_post_list(const std::string &term, Xapian::termcount wqf, double factor, TermFreqs *termfreqs)
Create a PostList object for term.
PostListAndEstimate make_synonym_postlist(PostListAndEstimate or_pl, double factor, const TermFreqs &termfreqs)
Create a SynonymPostList object.
const Xapian::Weight::Internal * get_stats() const
const Xapian::Database::Internal & db
void set_total_subqs(Xapian::termcount n)
void add_subquery(const Xapian::Query &subquery)
Xapian::Query::op get_op() const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
void postlist_sub_bool_or_like(OrContext &ctx, QueryOptimiser *qopt, TermFreqs *termfreqs) const
void postlist_sub_or_like(OrContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs, bool keep_zero_weight) const
std::string get_description() const
std::string get_description() const
Xapian::Query::op get_op() const
void serialise(std::string &result) const
bool postlist_sub_and_like(AndContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
Xapian::Internal::opt_intrusive_ptr< PostingSource > source
Definition: queryinternal.h:85
Xapian::Query::op get_type() const noexcept
void serialise(std::string &result) const
void serialise(std::string &result) const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
const Query get_subquery(size_t n) const
bool postlist_sub_and_like(AndContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
QueryScaleWeight(double factor, const Query &subquery_)
Xapian::Query::op get_type() const noexcept
void gather_terms(void *void_terms) const
size_t get_num_subqueries() const noexcept
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
Xapian::Query::op get_op() const
std::string get_description() const
void serialise(std::string &result) const
void gather_terms(void *void_terms) const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
bool postlist_sub_and_like(AndContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
Xapian::Query::op get_type() const noexcept
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
std::string get_description() const
void serialise(std::string &result) const
void serialise(std::string &result) const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
std::string get_description() const
Xapian::Query::op get_type() const noexcept
Xapian::Query::op get_type() const noexcept
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
void serialise(std::string &result) const
bool test_wildcard_(const std::string &candidate, size_t o, size_t p, size_t i) const
size_t head
Fixed head and tail lengths, and min/max length term that can match.
Xapian::Query::op get_type() const noexcept
QueryWildcard * change_combiner(Xapian::Query::op new_op)
Change the combining operator.
bool test_prefix_known(const std::string &candidate) const
Perform wildcard test on candidate known to match prefix.
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
void serialise(std::string &result) const
QueryWildcard(std::string_view pattern_, Xapian::termcount max_expansion_, int flags_, Query::op combiner_)
std::string get_description() const
termcount get_length() const noexcept
bool postlist_windowed(Xapian::Query::op op, AndContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
PostListAndEstimate postlist(QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
void postlist_sub_xor(XorContext &ctx, QueryOptimiser *qopt, double factor, TermFreqs *termfreqs) const
std::string get_description() const
Xapian::Query::op get_op() const
PostListAndEstimate postlist(TermFreqs *termfreqs)
XorContext(QueryOptimiser *qopt_, size_t reserve)
InvalidArgumentError indicates an invalid parameter value was passed to the API.
Definition: error.h:229
InvalidOperationError indicates the API was used in an invalid way.
Definition: error.h:271
Base class which provides an "external" source of postings.
Definition: postingsource.h:47
virtual PostingSource * unserialise_with_registry(const std::string &serialised, const Registry &registry) const
Create object given string serialisation returned by serialise().
PostingSource * release()
Start reference counting this object.
Class representing a query.
Definition: query.h:45
const Query get_subquery(size_t n) const
Read a top level subquery.
Definition: query.cc:289
op get_type() const noexcept
Get the type of the top level of the query.
Definition: query.cc:275
op
Query operators.
Definition: query.h:78
@ OP_SCALE_WEIGHT
Scale the weight contributed by a subquery.
Definition: query.h:166
@ LEAF_POSTING_SOURCE
Value returned by get_type() for a PostingSource.
Definition: query.h:283
@ OP_MAX
Pick the maximum weight of any subquery.
Definition: query.h:249
@ OP_VALUE_RANGE
Match only documents where a value slot is within a given range.
Definition: query.h:158
@ OP_WILDCARD
Wildcard expansion.
Definition: query.h:255
@ OP_XOR
Match documents which an odd number of subqueries match.
Definition: query.h:107
@ OP_AND_MAYBE
Match the first subquery taking extra weight from other subqueries.
Definition: query.h:118
@ LEAF_MATCH_ALL
Value returned by get_type() for MatchAll or equivalent.
Definition: query.h:290
@ OP_NEAR
Match only documents where all subqueries match near each other.
Definition: query.h:140
@ OP_ELITE_SET
Pick the best N subqueries and combine with OP_OR.
Definition: query.h:215
@ OP_AND
Match only documents which all subqueries match.
Definition: query.h:84
@ OP_OR
Match documents which at least one subquery matches.
Definition: query.h:92
@ OP_FILTER
Match like OP_AND but only taking weight from the first subquery.
Definition: query.h:128
@ OP_PHRASE
Match only documents where all subqueries match near and in order.
Definition: query.h:152
@ OP_VALUE_LE
Match only documents where a value slot is <= a given value.
Definition: query.h:231
@ OP_SYNONYM
Match like OP_OR but weighting as if a single term.
Definition: query.h:239
@ OP_AND_NOT
Match documents which the first subquery matches but no others do.
Definition: query.h:99
@ OP_EDIT_DISTANCE
Edit distance expansion.
Definition: query.h:269
@ LEAF_TERM
Value returned by get_type() for a term.
Definition: query.h:280
@ OP_VALUE_GE
Match only documents where a value slot is >= a given value.
Definition: query.h:223
@ OP_INVALID
Construct an invalid query.
Definition: query.h:277
bool empty() const noexcept
Check if this query is Xapian::Query::MatchNothing.
Definition: query.h:661
@ WILDCARD_PATTERN_MULTI
Support * which matches 0 or more characters.
Definition: query.h:330
@ WILDCARD_LIMIT_FIRST
Stop expanding when OP_WILDCARD reaches its expansion limit.
Definition: query.h:311
@ WILDCARD_LIMIT_MOST_FREQUENT
Limit OP_WILDCARD expansion to the most frequent terms.
Definition: query.h:321
@ WILDCARD_LIMIT_MASK_
Definition: query.h:324
@ WILDCARD_PATTERN_SINGLE
Support ? which matches a single character.
Definition: query.h:336
Xapian::Internal::intrusive_ptr< Internal > internal
Definition: query.h:48
Registry for user subclasses.
Definition: registry.h:47
const Xapian::PostingSource * get_posting_source(std::string_view name) const
Get a posting source given a name.
Definition: registry.cc:331
Indicates an error in the std::string serialisation of an object.
Definition: error.h:917
T::Internal *const * const_iterator
Definition: smallvector.h:515
bool empty() const
Definition: smallvector.h:450
std::size_t size() const
Definition: smallvector.h:438
const_iterator begin() const
Definition: smallvector.h:622
const_iterator end() const
Definition: smallvector.h:626
void push_back(const T &elt)
Definition: smallvector.h:632
Abstract base class for termlists.
Definition: termlist.h:42
virtual Internal * skip_to(std::string_view term)=0
Skip forward to the specified term.
virtual Internal * next()=0
Advance the current position to the next term in the termlist.
Suitable for "simple" type T.
Definition: smallvector.h:62
const_iterator end() const
Definition: smallvector.h:165
void reserve(size_type n)
Definition: smallvector.h:147
void erase(const_iterator it)
Definition: smallvector.h:229
void push_back(T elt)
Definition: smallvector.h:190
const_iterator begin() const
Definition: smallvector.h:161
WildcardError indicates an error expanding a wildcarded query.
Definition: error.h:1001
N-way XOR postlist.
Definition: xorpostlist.h:31
#define UNSIGNED_OVERFLOW_OK(X)
Definition: config.h:635
#define usual(COND)
Definition: config.h:617
#define rare(COND)
Definition: config.h:616
string term
PositionList * p
Xapian::termpos pos
Debug logging macros.
#define RETURN(...)
Definition: debuglog.h:484
#define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS)
Definition: debuglog.h:478
#define LOGCALL_VOID(CATEGORY, FUNC, PARAMS)
Definition: debuglog.h:479
Append a string to an object description, escaping invalid UTF-8.
Edit distance calculation algorithm.
Hierarchy of classes which Xapian can throw as exceptions.
Return docs containing terms forming a particular exact phrase.
Return document ids from an external source.
C++ STL heap implementation with extensions.
N-way OR postlist with wt=max(wt_i)
void pop(_RandomAccessIterator first, _RandomAccessIterator last, _Compare comp)
Definition: heap.h:213
void replace(_RandomAccessIterator first, _RandomAccessIterator last, _Compare comp)
Definition: heap.h:230
void make(_RandomAccessIterator first, _RandomAccessIterator last, _Compare comp)
Definition: heap.h:259
void sort(_RandomAccessIterator first, _RandomAccessIterator last, _Compare comp)
Definition: heap.h:277
string str(int value)
Convert int to std::string.
Definition: str.cc:91
static double string_frac(const string &s, size_t prefix)
static T estimate_and_not(T l, T r, U n)
static Xapian::doccount estimate_range_freq(const string &lo, const string &hi, const string &begin, const string *end, Xapian::doccount value_freq)
The Xapian namespace contains public interfaces for the Xapian library.
Definition: compactor.cc:82
unsigned XAPIAN_TERMCOUNT_BASE_TYPE termcount
A counts of terms.
Definition: types.h:64
unsigned valueno
The number for a value slot in a document.
Definition: types.h:90
unsigned XAPIAN_DOCID_BASE_TYPE doccount
A count of documents.
Definition: types.h:37
unsigned XAPIAN_DOCID_BASE_TYPE docid
A unique identifier for a document.
Definition: types.h:51
unsigned XAPIAN_TERMPOS_BASE_TYPE termpos
A term position within a document or query.
Definition: types.h:75
Return docs containing terms within a specified window.
Various assertion macros.
#define AssertEq(A, B)
Definition: omassert.h:124
#define AssertRel(A, REL, B)
Definition: omassert.h:123
#define Assert(COND)
Definition: omassert.h:122
Wrapper postlist providing positions for an OR.
PostList class implementing Query::OP_OR.
void unpack_throw_serialisation_error(const char *p)
Throw appropriate SerialisationError.
Definition: pack.cc:29
Pack types into strings and unpack them again.
bool unpack_string(const char **p, const char *end, std::string &result)
Decode a std::string from a string.
Definition: pack.h:468
bool unpack_uint(const char **p, const char *end, U *result)
Decode an unsigned integer from a string.
Definition: pack.h:346
void pack_uint(std::string &s, U value)
Append an encoded unsigned integer to a string.
Definition: pack.h:315
void pack_string(std::string &s, std::string_view value)
Append an encoded std::string to a string.
Definition: pack.h:442
void pack_string_empty(std::string &s)
Append an empty encoded std::string to a string.
Definition: pack.h:456
Return docs containing terms forming a particular phrase.
External sources of posting information.
Abstract base class for postlists.
Xapian::Query API class.
#define MISC(X)
#define MULTIWAY(X)
static constexpr unsigned MAX_UTF_8_CHARACTER_LENGTH
Xapian::Query internals.
Details passed around while building PostList tree from Query tree.
string serialise_double(double v)
Serialise a double to a string.
double unserialise_double(const char **p, const char *end)
Unserialise a double serialised by serialise_double.
functions to serialise and unserialise a double
Convert types to std::string.
Various handy string-related helpers.
bool endswith(std::string_view s, char sfx)
Definition: stringutils.h:80
bool startswith(std::string_view s, char pfx)
Definition: stringutils.h:56
Class providing an operator which sorts postlists to select max or terms.
bool operator()(PostList *a, PostList *b)
Return true if and only if a has a strictly greater termweight than b.
Comparison functor which orders by descending termfreq.
bool operator()(const PostList *a, const PostList *b) const
Order PostList* by descending get_termfreq().
The frequencies for a term.
Xapian::doccount reltermfreq
Xapian::termcount collfreq
Definition: header.h:242
Abstract base class for termlists.
#define U(C, D)
Definition: unicode-data.cc:59
Unicode and UTF-8 related classes and functions.
void description_append(std::string &desc, std::string_view s)
Definition: unittest.cc:105
Return document ids matching a >= test on a specified doc value.
Return document ids matching a range test on a specified doc value.
N-way XOR postlist.