xapian-core  2.1.0
matcher.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2006-2026 Olly Betts
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see
18  * <https://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 
23 #include "matcher.h"
24 
25 #include "api/enquireinternal.h"
26 #include "api/msetinternal.h"
27 #include "api/rsetinternal.h"
29 #include "deciderpostlist.h"
30 #include "localsubmatch.h"
31 #include "msetcmp.h"
32 #include "omassert.h"
33 #include "postlisttree.h"
34 #include "protomset.h"
35 #include "spymaster.h"
36 #include "valuestreamdocument.h"
37 #include "weight/weightinternal.h"
38 
39 #include <xapian/version.h> // For XAPIAN_HAS_REMOTE_BACKEND
40 
41 #ifdef XAPIAN_HAS_REMOTE_BACKEND
43 # include "remotesubmatch.h"
44 # include "socket_utils.h"
45 #endif
46 
47 #include <algorithm>
48 #include <cerrno>
49 #include <cfloat> // For DBL_EPSILON.
50 #include <vector>
51 
52 #ifdef HAVE_POLL_H
53 # include <poll.h>
54 #else
55 # include "safesysselect.h"
56 #endif
57 
58 #ifdef __WIN32__
59 # include "msvcignoreinvalidparam.h"
60 #endif
61 
62 using namespace std;
64 
65 static constexpr auto DOCID = Xapian::Enquire::Internal::DOCID;
66 static constexpr auto REL = Xapian::Enquire::Internal::REL;
68 static constexpr auto VAL = Xapian::Enquire::Internal::VAL;
70 
71 #ifdef XAPIAN_HAS_REMOTE_BACKEND
72 [[noreturn]]
73 static void unimplemented(const char* msg)
74 {
75  throw Xapian::UnimplementedError(msg);
76 }
77 
78 template<typename Action>
79 inline void
81 {
82 #ifdef HAVE_POLL
83  size_t n_remotes = remotes.size();
84  if (n_remotes <= 1) {
85  // We only need to use poll() when there are at least 2 remote
86  // databases we need to wait for.
87  if (n_remotes == 1) {
88  // Just execute action and block if it's not ready.
89  action(remotes[0].get());
90  }
91  return;
92  }
93 
94  unique_ptr<struct pollfd[]> fds(new struct pollfd[n_remotes]);
95  for (size_t i = 0; i != n_remotes; ++i) {
96  fds[i].fd = remotes[i]->get_read_fd();
97  fds[i].events = POLLIN;
98  fds[i].revents = 0;
99  }
100  do {
101  int r = poll(fds.get(), n_remotes, -1);
102  if (r <= 0) {
103  // We shouldn't get a timeout, but if we do retry.
104  if (r == 0 || errno == EINTR || errno == EAGAIN) {
105  continue;
106  }
107  throw Xapian::NetworkError("poll() failed waiting for remotes",
108  errno);
109  }
110  size_t i = 0;
111  while (i != n_remotes) {
112  if (fds[i].revents) {
113  action(remotes[i].get());
114  // Swap such that entries we still need to handle are first.
115  swap(remotes[i], remotes[--n_remotes]);
116  fds[i] = fds[n_remotes];
117  // r is number of ready fds.
118  if (--r == 0) break;
119  } else {
120  ++i;
121  }
122  }
123  } while (n_remotes > 1);
124 
125  // If there's only one remote left just execute action and block if it's
126  // not ready.
127  if (n_remotes == 1) {
128  action(remotes[0].get());
129  }
130 #else
131  size_t n_remotes = first_nonselectable;
132  fd_set fds;
133  while (n_remotes > 1) {
134  int nfds = 0;
135  FD_ZERO(&fds);
136  for (size_t i = 0; i != n_remotes; ++i) {
137  int fd = remotes[i]->get_read_fd();
138  FD_SET(fd, &fds);
139  if (fd >= nfds) nfds = fd + 1;
140  }
141 
142  int r = select(nfds, &fds, NULL, NULL, NULL);
143  if (r <= 0) {
144  int eno = socket_errno();
145  // We shouldn't get a timeout, but if we do retry.
146  if (r == 0 || eno == EINTR || eno == EAGAIN) {
147  continue;
148  }
149  throw Xapian::NetworkError("select() failed waiting for remotes",
150  eno);
151  }
152  size_t i = 0;
153  while (i != n_remotes) {
154  int fd = remotes[i]->get_read_fd();
155  if (FD_ISSET(fd, &fds)) {
156  action(remotes[i].get());
157  // Swap such that entries we still need to handle are first.
158  swap(remotes[i], remotes[--n_remotes]);
159  // r is number of ready fds.
160  if (--r == 0) break;
161  } else {
162  ++i;
163  }
164  }
165  }
166 
167  // If there's only one remote left just execute action and block if it's
168  // not ready.
169  if (n_remotes == 1) {
170  action(remotes[0].get());
171  }
172 
173  // Handle any remotes which we couldn't pass to select().
174  for (size_t i = first_nonselectable; i != remotes.size(); ++i) {
175  action(remotes[i].get());
176  }
177 #endif
178 }
179 #endif
180 
182  const Xapian::Query& query,
183  Xapian::termcount query_length,
184  const Xapian::RSet* rset,
186  const Xapian::Weight& wtscheme,
187  bool have_mdecider,
188  Xapian::valueno collapse_key,
189  Xapian::doccount collapse_max,
190  int percent_threshold,
191  double weight_threshold,
193  Xapian::valueno sort_key,
195  bool sort_val_reverse,
196  double time_limit,
197  const vector<opt_intrusive_ptr<Xapian::MatchSpy>>& matchspies)
198  : db(db_)
199 {
200  // An empty query should get handled higher up.
201  Assert(!query.empty());
202 
203  Xapian::doccount n_shards = db.internal->size();
204  vector<Xapian::RSet> subrsets;
205  if (rset && rset->internal) {
206  rset->internal->shard(n_shards, subrsets);
207  } else {
208  subrsets.resize(n_shards);
209  }
210 
211  for (Xapian::doccount i = 0; i != n_shards; ++i) {
212  const Xapian::Database::Internal *subdb = db.internal.get();
213  if (n_shards > 1) {
214  auto multidb = static_cast<const MultiDatabase*>(subdb);
215  subdb = multidb->shards[i];
216  }
217  Assert(subdb);
218 #ifdef XAPIAN_HAS_REMOTE_BACKEND
219  if (subdb->get_backend_info(NULL) == BACKEND_REMOTE) {
220  auto as_rem = static_cast<const RemoteDatabase*>(subdb);
221  if (have_mdecider) {
222  unimplemented("Xapian::MatchDecider not supported by the "
223  "remote backend");
224  }
225  as_rem->set_query(query, query_length,
226  collapse_key, collapse_max,
227  order, sort_key, sort_by, sort_val_reverse,
228  time_limit,
229  n_shards == 1 ? percent_threshold : 0,
230  weight_threshold,
231  wtscheme,
232  subrsets[i], matchspies);
233  remotes.emplace_back(new RemoteSubMatch(as_rem, i));
234  continue;
235  }
236 #else
237  // Avoid unused parameter warnings.
238  (void)have_mdecider;
239  (void)collapse_key;
240  (void)collapse_max;
241  (void)percent_threshold;
242  (void)weight_threshold;
243  (void)order;
244  (void)sort_key;
245  (void)sort_by;
246  (void)sort_val_reverse;
247  (void)time_limit;
248  (void)matchspies;
249 #endif /* XAPIAN_HAS_REMOTE_BACKEND */
250  if (locals.size() != i)
251  locals.resize(i);
252  locals.emplace_back(new LocalSubMatch(subdb, query, query_length,
253  wtscheme,
254  i));
255  subdb->readahead_for_query(query);
256  }
257 
258  if (!locals.empty() && locals.size() != n_shards)
259  locals.resize(n_shards);
260 
261 #ifdef XAPIAN_HAS_REMOTE_BACKEND
262 # ifndef HAVE_POLL
263 # ifndef __WIN32__
264  {
265  // Unfortunately POSIX select() can't monitor fds >= FD_SETSIZE, so
266  // swap those to the end here and then handle those last letting them
267  // just block if not ready.
268  first_nonselectable = remotes.size();
269  size_t i = 0;
270  while (i != first_nonselectable) {
271  int fd = remotes[i]->get_read_fd();
272  if (fd >= FD_SETSIZE) {
273  swap(remotes[i], remotes[--first_nonselectable]);
274  } else {
275  ++i;
276  }
277  }
278  }
279 # else
280  {
281  // We can only use select() on sockets under __WIN32__, but fds for
282  // remote prog databases aren't sockets, so go through and check if
283  // each fd is a socket or not, and swap the non-sockets to the end here
284  // and then handle those last letting them just block if not ready.
285  //
286  // FIXME: Perhaps we should use WaitForMultipleObjects() to allow
287  // waiting in parallel for prog databases too, but that seems a bit
288  // tricky to hook up as it probably needs an async ReadFile() to be
289  // active.
290  MSVCIgnoreInvalidParameter invalid_handle_value_is_ok;
291  first_nonselectable = remotes.size();
292  size_t i = 0;
293  while (i != first_nonselectable) {
294  int fd = remotes[i]->get_read_fd();
295  HANDLE handle = (HANDLE)_get_osfhandle(fd);
296  if (handle != INVALID_HANDLE_VALUE) {
297  // This fd isn't a socket.
298  swap(remotes[i], remotes[--first_nonselectable]);
299  } else {
300  ++i;
301  // On __WIN32__ FD_SETSIZE is the maximum number of sockets
302  // which can be added to an fd_set. It seems to be 64, so
303  // it's a case that's possible to trigger.
304  if (i == FD_SETSIZE) {
306  }
307  }
308  }
309  }
310 # endif
311 # endif
312 #endif
313 
314  stats.set_query(query);
315 
316  /* To improve overall performance in the case of searches over a mix of
317  * local and remote shards we set the queries for remote shards above,
318  * then prepare local shards here, then finish preparing remote shards
319  * below.
320  */
321 
322  if (!locals.empty()) {
323  // Prepare local matches.
324  for (Xapian::doccount i = 0; i != n_shards; ++i) {
325  auto submatch = locals[i].get();
326  if (submatch) {
327  submatch->prepare_match(subrsets[i], stats);
328  }
329  }
330  }
331 
332 #ifdef XAPIAN_HAS_REMOTE_BACKEND
334  [&](RemoteSubMatch* submatch) {
335  submatch->prepare_match(stats);
336  });
337 #endif
338 }
339 
342  Xapian::doccount maxitems,
343  Xapian::doccount check_at_least,
344  const Xapian::Weight& wtscheme,
345  const Xapian::MatchDecider* mdecider,
346  const Xapian::KeyMaker* sorter,
347  Xapian::valueno collapse_key,
348  Xapian::doccount collapse_max,
349  int percent_threshold,
350  double percent_threshold_factor,
351  double weight_threshold,
353  Xapian::valueno sort_key,
355  bool sort_val_reverse,
356  double time_limit,
357  const vector<opt_ptr_spy>& matchspies)
358 {
359  Assert(!locals.empty());
360 
361  ValueStreamDocument vsdoc(db);
362  ++vsdoc._refs;
363  Xapian::Document doc(&vsdoc);
364 
365  vector<PostList*> postlists;
366  postlists.reserve(locals.size());
367  PostListTree pltree(vsdoc, db, wtscheme);
368  Xapian::termcount total_subqs = 0;
375  Xapian::VecUniquePtr<EstimateOp> estimates(locals.size());
376  try {
377  bool all_null = true;
378  for (size_t i = 0; i != locals.size(); ++i) {
379  if (!locals[i]) {
380  postlists.push_back(nullptr);
381  estimates.push_back(nullptr);
382  continue;
383  }
384  // Pick the highest total subqueries answer amongst the
385  // subdatabases, as the query to postlist conversion doesn't
386  // recurse into positional queries for shards that don't have
387  // positional data when at least one other shard does.
388  Xapian::termcount total_subqs_i = 0;
389  PostListAndEstimate plest = locals[i]->get_postlist(&pltree,
390  &total_subqs_i);
391  total_subqs = max(total_subqs, total_subqs_i);
392  if (plest.pl != nullptr) {
393  all_null = false;
394  if (mdecider) {
395  plest.est.reset(new EstimateOp(EstimateOp::DECIDER,
396  plest.est.release()));
397  if (check_at_least) {
398  // No point creating the DeciderPostList if we aren't
399  // actually going to run the match.
400  plest.pl = new DeciderPostList(plest.pl,
401  plest.est.get(),
402  mdecider, &vsdoc,
403  &pltree);
404  }
405  }
406  }
407  postlists.push_back(plest.pl);
408  estimates.push_back(plest.est.release());
409  }
410  Assert(!postlists.empty());
411 
412  if (all_null) {
413  vector<Result> dummy;
414  return Xapian::MSet(new Xapian::MSet::Internal(first, 0, 0, 0, 0,
415  0, 0, 0.0, 0.0,
416  std::move(dummy),
417  0));
418  }
419  } catch (...) {
420  for (auto pl : postlists) delete pl;
421  throw;
422  }
423 
424  Xapian::doccount n_shards = postlists.size();
425 
426  // The highest weight a document could get in this match.
427  const double max_possible = pltree.set_postlists(&postlists[0], n_shards);
428  if (max_possible == 0.0) {
429  // All the weights are zero.
430  if (sort_by == REL) {
431  // We're only sorting by DOCID.
432  sort_by = DOCID;
433  } else if (sort_by == REL_VAL || sort_by == VAL_REL) {
434  // Normalise REL_VAL and VAL_REL to VAL, to avoid needlessly
435  // fetching and comparing weights.
436  sort_by = VAL;
437  }
438  // All percentages will be 100% so turn off any percentage cut-off.
439  percent_threshold = 0;
440  percent_threshold_factor = 0.0;
441  }
442 
443  // Check if any results have been asked for (might just be wanting
444  // maxweight).
445  if (check_at_least == 0) {
446  // Explicitly delete all PostList objects so they report any stats to
447  // the EstimateOp objects.
448  pltree.delete_postlists();
449  Xapian::doccount matches_lower_bound = 0;
450  Xapian::doccount matches_estimated = 0;
451  Xapian::doccount matches_upper_bound = 0;
452  for (size_t i = 0; i != estimates.size(); ++i) {
453  if (estimates[i]) {
454  Assert(locals[i].get());
455  Estimates e = locals[i]->resolve(estimates[i]);
456  matches_lower_bound += e.min;
457  matches_estimated += e.est;
458  matches_upper_bound += e.max;
459  }
460  }
461 
462  if (mdecider) {
463  matches_lower_bound = 0;
464  }
465 
466  Xapian::doccount uncollapsed_lower_bound = matches_lower_bound;
467  if (collapse_max) {
468  // Lower bound must be set to no more than collapse_max, since it's
469  // possible that all matching documents have the same collapse_key
470  // value and so are collapsed together.
471  if (matches_lower_bound > collapse_max)
472  matches_lower_bound = collapse_max;
473  }
474 
475  vector<Result> dummy;
476  return Xapian::MSet(new Xapian::MSet::Internal(first,
477  matches_upper_bound,
478  matches_lower_bound,
479  matches_estimated,
480  matches_upper_bound,
481  uncollapsed_lower_bound,
482  matches_estimated,
483  max_possible,
484  0.0,
485  std::move(dummy),
486  0));
487  }
488 
489  SpyMaster spymaster(&matchspies);
490 
491  bool sort_forward = (order != Xapian::Enquire::DESCENDING);
492  auto mcmp = get_msetcmp_function(sort_by, sort_forward, sort_val_reverse);
493 
494  // Can we stop once the ProtoMSet is full?
495  bool stop_once_full = (sort_forward &&
496  n_shards == 1 &&
497  sort_by == DOCID);
498 
499  ProtoMSet proto_mset(first, maxitems, check_at_least,
500  mcmp, sort_by, total_subqs,
501  pltree,
502  collapse_key, collapse_max,
503  percent_threshold, percent_threshold_factor,
504  max_possible,
505  stop_once_full,
506  time_limit);
507  proto_mset.set_new_min_weight(weight_threshold);
508 
509  while (true) {
510  double min_weight = proto_mset.get_min_weight();
511  if (!pltree.next(min_weight)) {
512  break;
513  }
514 
515  // The weight calculation can be expensive enough that it's worth being
516  // lazy and only calculating it once we know we need to. If sort_by
517  // is DOCID then all weights are zero.
518  double weight = 0.0;
519  bool calculated_weight = (sort_by == DOCID);
520  if (!calculated_weight) {
521  if (sort_by != VAL || min_weight > 0.0) {
522  weight = pltree.get_weight();
523  if (weight < min_weight) {
524  continue;
525  }
526  calculated_weight = true;
527  }
528  }
529 
530  Xapian::docid did = pltree.get_docid();
531  vsdoc.set_document(did);
532  Result new_item(weight, did);
533 
534  if (sort_by != DOCID && sort_by != REL) {
535  if (sorter) {
536  new_item.set_sort_key((*sorter)(doc));
537  } else {
538  new_item.set_sort_key(vsdoc.get_value(sort_key));
539  }
540 
541  if (proto_mset.early_reject(new_item, calculated_weight, spymaster,
542  doc))
543  continue;
544  }
545 
546  // Apply any MatchSpy objects.
547  if (spymaster) {
548  if (!calculated_weight) {
549  weight = pltree.get_weight();
550  new_item.set_weight(weight);
551  calculated_weight = true;
552  }
553  spymaster(doc, weight);
554  }
555 
556  if (!calculated_weight) {
557  weight = pltree.get_weight();
558  new_item.set_weight(weight);
559  }
560 
561  if (!proto_mset.process(std::move(new_item), vsdoc))
562  break;
563  }
564 
565  // Explicitly delete all PostList objects so they report any stats to
566  // the EstimateOp objects.
567  pltree.delete_postlists();
568 
569  return proto_mset.finalise(mdecider,
570  locals,
571  estimates,
572  maxitems);
573 }
574 
577  Xapian::doccount maxitems,
578  Xapian::doccount check_at_least,
580  const Xapian::Weight& wtscheme,
581  const Xapian::MatchDecider* mdecider,
582  const Xapian::KeyMaker* sorter,
583  Xapian::valueno collapse_key,
584  Xapian::doccount collapse_max,
585  int percent_threshold,
586  double weight_threshold,
588  Xapian::valueno sort_key,
590  bool sort_val_reverse,
591  double time_limit,
592  const vector<opt_intrusive_ptr<Xapian::MatchSpy>>& matchspies)
593 {
594  AssertRel(check_at_least, >=, first + maxitems);
595 
596 #ifdef XAPIAN_HAS_REMOTE_BACKEND
597  if (locals.empty() && remotes.size() == 1) {
598  // Short cut for a single remote database.
599  Assert(remotes[0]);
600  remotes[0]->start_match(first, maxitems, check_at_least, sorter,
601  stats);
602  return remotes[0]->get_mset(matchspies);
603  }
604 #endif
605 
606  // Factor to multiply maximum weight seen by to get the minimum weight we
607  // need to consider.
608  double percent_threshold_factor = percent_threshold / 100.0;
609  // Corresponding correction to that in api/mset.cc to account for excess
610  // precision on x86.
611  percent_threshold_factor -= DBL_EPSILON;
612 
613 #ifdef XAPIAN_HAS_REMOTE_BACKEND
614  for (auto&& submatch : remotes) {
615  Assert(submatch);
616  // We need to fetch the first "first" results too, as merging may push
617  // those down into the part of the merged MSet we care about.
618  Xapian::doccount remote_maxitems = first + maxitems;
619  if (collapse_max != 0) {
620  // If collapsing we need to fetch all check_at_least items in order
621  // to satisfy the requirement that if there are <= check_at_least
622  // results then then estimated number of matches is exact.
623  AssertRel(check_at_least, >=, first + maxitems);
624  remote_maxitems = check_at_least;
625  }
626  submatch->start_match(0, remote_maxitems, check_at_least, sorter,
627  stats);
628  }
629 #endif
630 
631  Xapian::MSet local_mset;
632  if (!locals.empty()) {
633  for (auto&& submatch : locals) {
634  if (submatch)
635  submatch->start_match(stats);
636  }
637 
638  Xapian::doccount local_first = first;
639  Xapian::doccount local_maxitems = maxitems;
640  double local_percent_threshold_factor = percent_threshold_factor;
641 #ifdef XAPIAN_HAS_REMOTE_BACKEND
642  if (!remotes.empty()) {
643  // We need to fetch the first "first" results too, as merging may
644  // push those down into the part of the merged MSet we care about.
645  local_first = 0;
646  local_maxitems = first + maxitems;
647  if (collapse_max != 0) {
648  // If collapsing we need to fetch all check_at_least items in
649  // order to satisfy the requirement that if there are <=
650  // check_at_least results then then estimated number of matches
651  // is exact. FIXME: Can we avoid this for the local shard by
652  // making use of information in the Collapser?
653  AssertRel(check_at_least, >=, first + maxitems);
654  local_maxitems = check_at_least;
655  }
656  local_percent_threshold_factor = 0.0;
657  }
658 #endif
659  local_mset = get_local_mset(local_first, local_maxitems, check_at_least,
660  wtscheme, mdecider,
661  sorter, collapse_key, collapse_max,
662  percent_threshold,
663  local_percent_threshold_factor,
664  weight_threshold, order, sort_key, sort_by,
665  sort_val_reverse, time_limit, matchspies);
666  }
667 
668 #ifdef XAPIAN_HAS_REMOTE_BACKEND
669  if (remotes.empty()) {
670  // Another easy case - only local databases.
671  return local_mset;
672  }
673 
674  // We need to merge MSet objects. We only need the number of remote shards
675  // + 1 if there are any local shards, so reserving n_shards may be more
676  // than we need.
677  vector<pair<Xapian::MSet, Xapian::doccount>> msets;
678  Xapian::MSet merged_mset;
680  [&](RemoteSubMatch* submatch) {
681  Xapian::MSet remote_mset = submatch->get_mset(matchspies);
682  merged_mset.internal->merge_stats(remote_mset.internal.get(),
683  collapse_max != 0);
684  auto& merged_stats = merged_mset.internal->stats;
685  if (!merged_stats) {
686  merged_stats = std::move(remote_mset.internal->stats);
687  } else {
688  merged_stats->merge(*(remote_mset.internal->stats));
689  }
690  if (remote_mset.empty()) {
691  return;
692  }
693  remote_mset.internal->unshard_docids(submatch->get_shard(),
694  db.internal->size());
695  msets.push_back({remote_mset, 0});
696  });
697 
698  if (!locals.empty()) {
699  if (!local_mset.empty())
700  msets.push_back({local_mset, 0});
701  merged_mset.internal->merge_stats(local_mset.internal.get(),
702  collapse_max != 0);
703  merged_mset.internal->stats->merge(stats);
704  }
705 
706  if (merged_mset.internal->max_possible == 0.0) {
707  // All the weights are zero.
708  if (sort_by == REL) {
709  // We're only sorting by DOCID.
710  sort_by = DOCID;
711  } else if (sort_by == REL_VAL || sort_by == VAL_REL) {
712  // Normalise REL_VAL and VAL_REL to VAL, to avoid needlessly
713  // fetching and comparing weights.
714  sort_by = VAL;
715  }
716  // All percentages will be 100% so turn off any percentage cut-off.
717  percent_threshold = 0;
718  percent_threshold_factor = 0.0;
719  }
720 
721  bool sort_forward = (order != Xapian::Enquire::DESCENDING);
722  auto mcmp = get_msetcmp_function(sort_by, sort_forward, sort_val_reverse);
723  auto heap_cmp =
724  [&](const pair<Xapian::MSet, Xapian::doccount>& a,
725  const pair<Xapian::MSet, Xapian::doccount>& b) {
726  return mcmp(b.first.internal->items[b.second],
727  a.first.internal->items[a.second]);
728  };
729 
730  Heap::make(msets.begin(), msets.end(), heap_cmp);
731 
732  double min_weight = 0.0;
733  if (percent_threshold) {
734  min_weight = percent_threshold_factor * 100.0 /
735  merged_mset.internal->percent_scale_factor;
736  }
737 
738  CollapserLite collapser(collapse_max);
739  merged_mset.internal->first = first;
740  while (!msets.empty() && merged_mset.size() != maxitems) {
741  auto& front = msets.front();
742  auto& result = front.first.internal->items[front.second];
743  if (percent_threshold) {
744  if (result.get_weight() < min_weight) {
745  // FIXME: This will need adjusting if we ever support
746  // percentage thresholds when sorting primarily by value.
747  break;
748  }
749  }
750  if (!collapser || collapser.add(result.get_collapse_key())) {
751  if (first) {
752  // Skip the first "first" results from the merge - we had to
753  // also fetch the first "first" results from each shard, as
754  // merging may push those down into the part of the merged MSet
755  // we care about.
756  --first;
757  } else {
758  merged_mset.internal->items.push_back(std::move(result));
759  }
760  }
761  auto n = front.second + 1;
762  if (n == front.first.size()) {
763  Heap::pop(msets.begin(), msets.end(), heap_cmp);
764  msets.resize(msets.size() - 1);
765  } else {
766  front.second = n;
767  Heap::replace(msets.begin(), msets.end(), heap_cmp);
768  }
769  }
770 
771  if (collapser) {
772  auto todo = check_at_least - maxitems;
773  if (merged_mset.size() != maxitems) {
774  todo = 0;
775  }
776  for ( ; !msets.empty() && todo; --todo) {
777  auto& front = msets.front();
778  auto& result = front.first.internal->items[front.second];
779  if (percent_threshold) {
780  if (result.get_weight() < min_weight) {
781  // FIXME: This will need adjusting if we ever support
782  // percentage thresholds when sorting primarily by value.
783  break;
784  }
785  }
786  (void)collapser.add(result.get_collapse_key());
787  auto n = front.second + 1;
788  if (n == front.first.size()) {
789  Heap::pop(msets.begin(), msets.end(), heap_cmp);
790  msets.resize(msets.size() - 1);
791  } else {
792  front.second = n;
793  Heap::replace(msets.begin(), msets.end(), heap_cmp);
794  }
795  }
796 
797  auto mseti = merged_mset.internal;
798  collapser.finalise(mseti->items, percent_threshold);
799 
800  if (check_at_least > 0) {
801  // Each input MSet object to the merge has already been collapsed
802  // and merge_stats() above will have set mset->matches_lower_bound
803  // to the maximum matches_lower_bound of any input, which provides
804  // a lower bound.
805  //
806  // In some cases, the collapser can provide a better lower bound.
807  auto collapser_lb = collapser.get_matches_lower_bound();
808  if (mseti->matches_upper_bound <= check_at_least) {
809  mseti->matches_lower_bound = collapser_lb;
810  mseti->matches_estimated = collapser_lb;
811  mseti->matches_upper_bound = collapser_lb;
812  return merged_mset;
813  }
814 
815  mseti->matches_lower_bound = max(mseti->matches_lower_bound,
816  collapser_lb);
817  }
818 
819  double unique_rate = 1.0;
820 
821  Xapian::doccount docs_considered = collapser.get_docs_considered();
822  Xapian::doccount dups_ignored = collapser.get_dups_ignored();
823  if (docs_considered > 0) {
824  // Scale the estimate by the rate at which we've been finding
825  // unique documents while merging MSet objects.
826  double unique = double(docs_considered - dups_ignored);
827  unique_rate = unique / double(docs_considered);
828  }
829 
830  // We can safely reduce the upper bound by the number of duplicates
831  // we've seen while merging MSet objects.
832  mseti->matches_upper_bound -= collapser.get_dups_ignored();
833 
834  double estimate_scale = unique_rate;
835 
836  if (estimate_scale != 1.0) {
837  auto l = mseti->matches_lower_bound;
838  auto u = mseti->matches_upper_bound;
839  auto e = l + Xapian::doccount((u - l) * estimate_scale + 0.5);
840  mseti->matches_estimated = e;
841  }
842 
843  // Clamp the estimate the range given by the bounds.
844  AssertRel(mseti->matches_lower_bound, <=, mseti->matches_upper_bound);
845  mseti->matches_estimated = std::clamp(mseti->matches_estimated,
846  mseti->matches_lower_bound,
847  mseti->matches_upper_bound);
848  }
849 
850  return merged_mset;
851 #else
852  return local_mset;
853 #endif
854 }
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
if(!(properties &BACKEND))
Definition: api_collated.h:3
@ BACKEND_REMOTE
Definition: backends.h:27
Simpler version of Collapser used when merging MSet objects.
Definition: collapser.h:260
PostList which applies a MatchDecider.
Class for estimating the total number of matching documents.
Definition: estimateop.h:64
Xapian::MSet get_mset(Xapian::doccount first, Xapian::doccount maxitems, Xapian::doccount check_at_least, Xapian::Weight::Internal &stats, const Xapian::Weight &wtscheme, const Xapian::MatchDecider *mdecider, const Xapian::KeyMaker *sorter, Xapian::valueno collapse_key, Xapian::doccount collapse_max, int percent_threshold, double weight_threshold, Xapian::Enquire::docid_order order, Xapian::valueno sort_key, Xapian::Enquire::Internal::sort_setting sort_by, bool sort_val_reverse, double time_limit, const std::vector< opt_ptr_spy > &matchspies)
Run the match and produce an MSet object.
Definition: matcher.cc:576
std::vector< std::unique_ptr< LocalSubMatch > > locals
LocalSubMatch objects for local databases.
Definition: matcher.h:56
Xapian::Database db
Definition: matcher.h:49
std::size_t first_nonselectable
Partition point in remotes.
Definition: matcher.h:86
Xapian::MSet get_local_mset(Xapian::doccount first, Xapian::doccount maxitems, Xapian::doccount check_at_least, const Xapian::Weight &wtscheme, const Xapian::MatchDecider *mdecider, const Xapian::KeyMaker *sorter, Xapian::valueno collapse_key, Xapian::doccount collapse_max, int percent_threshold, double percent_threshold_factor, double weight_threshold, Xapian::Enquire::docid_order order, Xapian::valueno sort_key, Xapian::Enquire::Internal::sort_setting sort_by, bool sort_val_reverse, double time_limit, const std::vector< opt_ptr_spy > &matchspies)
Definition: matcher.cc:341
std::vector< std::unique_ptr< RemoteSubMatch > > remotes
RemoteSubMatch objects for remote databases.
Definition: matcher.h:78
void for_all_remotes(Action action)
Perform action on remotes as they become ready using poll() or select().
Definition: matcher.cc:80
Matcher(const Matcher &)=delete
Sharded database backend.
double set_postlists(PostList **pls, Xapian::doccount n_shards_)
Definition: postlisttree.h:109
Xapian::docid get_docid() const
Definition: postlisttree.h:158
bool next(double w_min)
Return false if we're done.
Definition: postlisttree.h:173
double get_weight() const
Definition: postlisttree.h:166
void delete_postlists()
Delete all the PostList objects.
Definition: postlisttree.h:90
bool process(Result &&new_item, ValueStreamDocument &vsdoc)
Process new_item.
Definition: protomset.h:303
bool early_reject(Result &new_item, bool calculated_weight, SpyMaster &spymaster, const Xapian::Document &doc)
Definition: protomset.h:252
void set_new_min_weight(double min_wt)
Definition: protomset.h:434
double get_min_weight() const
Definition: protomset.h:176
Xapian::MSet finalise(const Xapian::MatchDecider *mdecider, const std::vector< std::unique_ptr< LocalSubMatch >> &locals, const Xapian::VecUniquePtr< EstimateOp > &estimates, Xapian::doccount max_items)
Definition: protomset.h:493
RemoteDatabase is the baseclass for remote database implementations.
Class for performing matching on a remote database.
void start_match(Xapian::doccount first, Xapian::doccount maxitems, Xapian::doccount check_at_least, const Xapian::KeyMaker *sorter, const Xapian::Weight::Internal &total_stats)
Start the match.
Xapian::doccount get_shard() const
Return the index of the corresponding Database shard.
Xapian::MSet get_mset(const std::vector< opt_ptr_spy > &matchspies)
Get MSet.
void prepare_match(Xapian::Weight::Internal &total_stats)
Fetch and collate statistics.
A result in an MSet.
Definition: result.h:30
void set_weight(double weight_)
Definition: result.h:78
void set_sort_key(const std::string &k)
Definition: result.h:84
A document which gets its values from a ValueStreamManager.
void set_document(Xapian::docid did_)
std::string get_value(Xapian::valueno slot) const
Virtual base class for Database internals.
virtual int get_backend_info(std::string *path) const =0
Get backend information about this database.
virtual void readahead_for_query(const Query &query) const
An indexed database of documents.
Definition: database.h:75
Xapian::Internal::intrusive_ptr_nonnull< Internal > internal
Definition: database.h:95
Class representing a document.
Definition: document.h:64
docid_order
Ordering of docids.
Definition: enquire.h:130
@ DESCENDING
docids sort in descending order.
Definition: enquire.h:134
unsigned _refs
Reference count.
Definition: intrusive_ptr.h:74
A smart pointer that optionally uses intrusive reference counting.
Virtual base class for key making functors.
Definition: keymaker.h:44
Xapian::MSet internals.
Definition: msetinternal.h:44
Class representing a list of search results.
Definition: mset.h:46
Xapian::Internal::intrusive_ptr_nonnull< Internal > internal
Definition: mset.h:78
bool empty() const
Return true if this MSet object is empty.
Definition: mset.h:471
Abstract base class for match deciders.
Definition: matchdecider.h:37
Indicates a problem communicating with a remote database.
Definition: error.h:791
Class representing a query.
Definition: query.h:45
bool empty() const noexcept
Check if this query is Xapian::Query::MatchNothing.
Definition: query.h:661
Class representing a set of documents judged as relevant.
Definition: rset.h:39
Xapian::Internal::intrusive_ptr< Internal > internal
Definition: rset.h:42
UnimplementedError indicates an attempt to use an unimplemented feature.
Definition: error.h:313
Suitable for "simple" type T.
Definition: smallvector.h:62
void push_back(T elt)
Definition: smallvector.h:190
size_type size() const
Definition: smallvector.h:135
Class to hold statistics for a given collection.
void set_query(const Xapian::Query &query_)
Abstract base class for weighting schemes.
Definition: weight.h:38
PostList which applies a MatchDecider.
Xapian::Enquire internals.
SubMatch class for a local database.
static void unimplemented(const char *msg)
Definition: matcher.cc:73
static constexpr auto DOCID
Definition: matcher.cc:65
static constexpr auto REL
Definition: matcher.cc:66
static constexpr auto VAL_REL
Definition: matcher.cc:69
static constexpr auto REL_VAL
Definition: matcher.cc:67
static constexpr auto VAL
Definition: matcher.cc:68
Matcher class.
MSetCmp get_msetcmp_function(Xapian::Enquire::Internal::sort_setting sort_by, bool sort_forward, bool sort_val_reverse)
Select the appropriate msetcmp function.
Definition: msetcmp.cc:100
Result comparison functions.
Xapian::MSet internals.
Work around MSVC's unhelpful non-standard invalid parameter handling.
Sharded database backend.
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
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
Various assertion macros.
#define AssertRel(A, REL, B)
Definition: omassert.h:123
#define Assert(COND)
Definition: omassert.h:122
Class for managing a tree of PostList objects.
ProtoMSet class.
RemoteDatabase is the baseclass for remote database implementations.
SubMatch class for a remote database.
Set of documents judged as relevant.
include <sys/select.h> with portability workarounds.
Socket handling utilities.
int socket_errno()
Definition: socket_utils.h:121
Class for managing MatchSpy objects during the match.
Xapian::doccount est
Definition: estimateop.h:33
Xapian::doccount min
Definition: estimateop.h:33
Xapian::doccount max
Definition: estimateop.h:33
std::unique_ptr< EstimateOp > est
Definition: estimateop.h:217
A document which gets its values from a ValueStreamManager.
Define preprocessor symbols for the library version.
const char * dummy[]
Definition: version_h.cc:7
Xapian::Weight::Internal class, holding database and term statistics.