xapian-core  2.0.0
api_db.cc
Go to the documentation of this file.
1 
4 /* Copyright 1999,2000,2001 BrightStation PLC
5  * Copyright 2002 Ananova Ltd
6  * Copyright 2002-2023 Olly Betts
7  * Copyright 2006,2007,2008,2009 Lemur Consulting Ltd
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 2 of the
12  * License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, see
21  * <https://www.gnu.org/licenses/>.
22  */
23 
24 #include <config.h>
25 
26 #include "api_db.h"
27 
28 #include <algorithm>
29 #include <fstream>
30 #include <map>
31 #include <string>
32 #include <vector>
33 #include "safenetdb.h" // For gai_strerror().
34 #include "safesysstat.h" // For mkdir().
35 #include "safeunistd.h" // For sleep().
36 
37 #include <xapian.h>
38 
39 #include "backendmanager.h"
40 #include "testsuite.h"
41 #include "testutils.h"
42 #include "unixcmds.h"
43 
44 #include "apitest.h"
45 
46 using namespace std;
47 
48 static Xapian::Query
49 query(const string &t)
50 {
51  return Xapian::Query(Xapian::Stem("english")(t));
52 }
53 
54 // #######################################################################
55 // # Tests start here
56 
57 // tests Xapian::Database::get_termfreq() and Xapian::Database::term_exists()
58 DEFINE_TESTCASE(termstats, backend) {
59  Xapian::Database db(get_database("apitest_simpledata"));
60 
61  TEST(!db.term_exists("corn"));
62  TEST_EQUAL(db.get_termfreq("corn"), 0);
63  TEST(db.term_exists("banana"));
64  TEST_EQUAL(db.get_termfreq("banana"), 1);
65  TEST(db.term_exists("paragraph"));
66  TEST_EQUAL(db.get_termfreq("paragraph"), 5);
67 }
68 
69 // Check that stub databases work.
70 DEFINE_TESTCASE(stubdb1, check && path) {
71  mkdir(".stub", 0755);
72  const char * dbpath = ".stub/stubdb1";
73  ofstream out(dbpath);
74  TEST(out.is_open());
75  out << "auto ../" << get_database_path("apitest_simpledata") << '\n';
76  out.close();
77 
78  {
80  Xapian::Enquire enquire(db);
81  enquire.set_query(Xapian::Query("word"));
82  enquire.get_mset(0, 10);
83  }
84  {
85  Xapian::Database db(dbpath);
86  Xapian::Enquire enquire(db);
87  enquire.set_query(Xapian::Query("word"));
88  enquire.get_mset(0, 10);
89  }
90 
92 }
93 
94 // Check that stub databases work remotely.
95 DEFINE_TESTCASE(stubdb2, path) {
96  mkdir(".stub", 0755);
97  const char * dbpath = ".stub/stubdb2";
98  ofstream out(dbpath);
99  TEST(out.is_open());
100  out << "remote :" << BackendManager::get_xapian_progsrv_command()
101  << ' ' << get_database_path("apitest_simpledata") << '\n';
102  out.close();
103 
104  try {
106  Xapian::Enquire enquire(db);
107  enquire.set_query(Xapian::Query("word"));
108  enquire.get_mset(0, 10);
110 #ifdef XAPIAN_HAS_REMOTE_BACKEND
111  throw;
112 #endif
113  }
114 
115  try {
116  Xapian::Database db(dbpath);
117  Xapian::Enquire enquire(db);
118  enquire.set_query(Xapian::Query("word"));
119  enquire.get_mset(0, 10);
121 #ifdef XAPIAN_HAS_REMOTE_BACKEND
122  throw;
123 #endif
124  }
125 
126  out.open(dbpath);
127  TEST(out.is_open());
128  out << "remote\n";
129  out.close();
130 
131  // Quietly ignored prior to 1.4.1.
134  );
135 
136  // Quietly ignored prior to 1.4.1.
139  );
140 
141 #ifdef XAPIAN_HAS_REMOTE_BACKEND
142 # define EXPECTED_EXCEPTION Xapian::DatabaseOpeningError
143 #else
144 # define EXPECTED_EXCEPTION Xapian::FeatureUnavailableError
145 #endif
146 
147  out.open(dbpath);
148  TEST(out.is_open());
149  out << "remote foo\n";
150  out.close();
151 
152  // Quietly ignored prior to 1.4.1.
155  );
156 
157  // Quietly ignored prior to 1.4.1.
160  );
161 
162 #ifdef XAPIAN_HAS_REMOTE_BACKEND
163  out.open(dbpath);
164  TEST(out.is_open());
165  out << "remote [::1]:65535\n";
166  out.close();
167 
168  try {
170  } catch (const Xapian::NetworkError& e) {
171  // 1.4.0 threw (on Linux) the confusing message:
172  // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
173  // 1.4.1 throws (because we don't actually support IPv6 yet) on Linux (EAI_ADDRFAMILY):
174  // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (nodename nor servname provided, or not known)
175  // or on macOS (EAI_NONAME):
176  // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (Address family for hostname not supported)
177  //
178  // But NetBSD seems to resolve ::1 to an IPv4 address and then tries
179  // to connect to it (which hopefully fails), so just test the message
180  // doesn't match the bad 1.4.0 result.
181  TEST(e.get_msg().find("host [") == string::npos);
182  TEST_EQUAL(e.get_context(), "remote:tcp(::1:65535)");
183  }
184 
185  try {
187  } catch (const Xapian::NetworkError& e) {
188  // 1.4.0 threw (Linux):
189  // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
190  // 1.4.1 throws (because we don't actually support IPv6 yet) on Linux (EAI_ADDRFAMILY):
191  // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (nodename nor servname provided, or not known)
192  // or on macOS (EAI_NONAME):
193  // NetworkError: Couldn't resolve host ::1 (context: remote:tcp(::1:65535)) (Address family for hostname not supported)
194  // So we test the message instead of the error string for portability.
195  //
196  // But NetBSD seems to resolve ::1 to an IPv4 address and then tries
197  // to connect to it (which hopefully fails), so just test the message
198  // doesn't match the bad 1.4.0 result.
199  TEST(e.get_msg().find("host [") == string::npos);
200  TEST_EQUAL(e.get_context(), "remote:tcp(::1:65535)");
201  }
202 #endif
203 
204  out.open(dbpath);
205  TEST(out.is_open());
206  // Invalid - the port number is required.
207  out << "remote [::1]\n";
208  out.close();
209 
210  // 1.4.0 threw:
211  // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
214  );
215 
216  // 1.4.0 threw:
217  // NetworkError: Couldn't resolve host [ (context: remote:tcp([:0)) (No address associated with hostname)
220  );
221 }
222 
223 // Regression test - bad entries were ignored after a good entry prior to 1.0.8.
224 DEFINE_TESTCASE(stubdb3, path) {
225  mkdir(".stub", 0755);
226  const char * dbpath = ".stub/stubdb3";
227  ofstream out(dbpath);
228  TEST(out.is_open());
229  out << "auto ../" << get_database_path("apitest_simpledata") << "\n"
230  "bad line here\n";
231  out.close();
232 
235 
237  Xapian::Database db(dbpath));
238 }
239 
240 // Test a stub database with just a bad entry.
241 DEFINE_TESTCASE(stubdb4, !backend) {
242  mkdir(".stub", 0755);
243  const char * dbpath = ".stub/stubdb4";
244  ofstream out(dbpath);
245  TEST(out.is_open());
246  out << "bad line here\n";
247  out.close();
248 
251 
253  Xapian::Database db(dbpath));
254 }
255 
256 // Test a stub database with a bad entry with no spaces (prior to 1.1.0 this
257 // was deliberately allowed, though not documented.
258 DEFINE_TESTCASE(stubdb5, path) {
259  mkdir(".stub", 0755);
260  const char * dbpath = ".stub/stubdb5";
261  ofstream out(dbpath);
262  TEST(out.is_open());
263  out << "bad\n"
264  "auto ../" << get_database_path("apitest_simpledata") << '\n';
265  out.close();
266 
269 
271  Xapian::Database db(dbpath));
272 }
273 
274 // Test a stub database with an inmemory database (new feature in 1.1.0).
275 DEFINE_TESTCASE(stubdb6, inmemory) {
276  mkdir(".stub", 0755);
277  const char * dbpath = ".stub/stubdb6";
278  ofstream out(dbpath);
279  TEST(out.is_open());
280  out << "inmemory\n";
281  out.close();
282 
283  // Read-only tests:
284  {
286  TEST_EQUAL(db.get_doccount(), 0);
287  Xapian::Enquire enquire(db);
288  enquire.set_query(Xapian::Query("word"));
289  Xapian::MSet mset = enquire.get_mset(0, 10);
290  TEST(mset.empty());
291  }
292  {
293  Xapian::Database db(dbpath);
294  TEST_EQUAL(db.get_doccount(), 0);
295  Xapian::Enquire enquire(db);
296  enquire.set_query(Xapian::Query("word"));
297  Xapian::MSet mset = enquire.get_mset(0, 10);
298  TEST(mset.empty());
299  }
300 
301  // Writable tests:
302  {
303  Xapian::WritableDatabase db(dbpath,
305  TEST_EQUAL(db.get_doccount(), 0);
307  TEST_EQUAL(db.get_doccount(), 1);
308  }
309  {
310  Xapian::WritableDatabase db(dbpath,
312  TEST_EQUAL(db.get_doccount(), 0);
314  TEST_EQUAL(db.get_doccount(), 1);
315  }
316 }
317 
319 // Regression test - in 1.4.3 and earlier this threw
320 // Xapian::DatabaseError.
321 DEFINE_TESTCASE(stubdb8, inmemory) {
322  mkdir(".stub", 0755);
323  const char * dbpath = ".stub/stubdb8";
324  ofstream out(dbpath);
325  TEST(out.is_open());
326  out << "inmemory\n";
327  out.close();
328 
329  try {
330  Xapian::Database::check(dbpath);
331  FAIL_TEST("Managed to check inmemory stub");
332  } catch (const Xapian::UnimplementedError& e) {
333  // Check the message is appropriate.
335  "InMemory database checking not implemented");
336  }
337 }
338 
340  string needle;
341  public:
342  explicit GrepMatchDecider(const string& needle_)
343  : needle(needle_) {}
344 
345  bool operator()(const Xapian::Document& doc) const override {
346  // Note that this is not recommended usage of get_data()
347  return doc.get_data().find(needle) != string::npos;
348  }
349 };
350 
351 // Test Xapian::MatchDecider functor.
352 DEFINE_TESTCASE(matchdecider1, backend && !remote) {
353  Xapian::Database db(get_database("apitest_simpledata"));
354  Xapian::Enquire enquire(db);
355  enquire.set_query(Xapian::Query("this"));
356 
357  GrepMatchDecider myfunctor("This is");
358 
359  Xapian::MSet mymset = enquire.get_mset(0, 100, 0, &myfunctor);
360 
361  vector<bool> docid_checked(db.get_lastdocid());
362 
363  // Check that we get the expected number of matches, and that they
364  // satisfy the condition.
365  Xapian::MSetIterator i = mymset.begin();
366  TEST(i != mymset.end());
367  TEST_EQUAL(mymset.size(), 3);
368  TEST_EQUAL(mymset.get_matches_lower_bound(), 3);
369  TEST_EQUAL(mymset.get_matches_upper_bound(), 3);
370  TEST_EQUAL(mymset.get_matches_estimated(), 3);
374  for ( ; i != mymset.end(); ++i) {
375  const Xapian::Document doc(i.get_document());
376  TEST(myfunctor(doc));
377  docid_checked[*i] = true;
378  }
379 
380  // Check that there are some documents which aren't accepted by the match
381  // decider.
382  mymset = enquire.get_mset(0, 100);
383  TEST(mymset.size() > 3);
384 
385  // Check that the bounds are appropriate even if we don't ask for any
386  // actual matches.
387  mymset = enquire.get_mset(0, 0, 0, &myfunctor);
388  TEST_EQUAL(mymset.size(), 0);
389  TEST_EQUAL(mymset.get_matches_lower_bound(), 0);
390  TEST_EQUAL(mymset.get_matches_upper_bound(), 6);
391  TEST_REL(mymset.get_matches_estimated(),>,0);
392  TEST_REL(mymset.get_matches_estimated(),<=,6);
397 
398  // Check that the bounds are appropriate if we ask for only one hit.
399  // (Regression test - until SVN 10256, we didn't reduce the lower_bound
400  // appropriately, and returned 6 here.)
401  mymset = enquire.get_mset(0, 1, 0, &myfunctor);
402  TEST_EQUAL(mymset.size(), 1);
403  TEST_REL(mymset.get_matches_lower_bound(),>=,1);
404  TEST_REL(mymset.get_matches_lower_bound(),<=,3);
405  TEST_REL(mymset.get_matches_upper_bound(),>=,3);
406  TEST_REL(mymset.get_matches_upper_bound(),<=,6);
407  TEST_REL(mymset.get_matches_estimated(),>,0);
408  TEST_REL(mymset.get_matches_estimated(),<=,6);
415 
416  // Check that the other documents don't satisfy the condition.
417  for (Xapian::docid did = 1; did < docid_checked.size(); ++did) {
418  if (!docid_checked[did]) {
419  TEST(!myfunctor(db.get_document(did)));
420  }
421  }
422 
423  // Check that the bounds are appropriate if a collapse key is used.
424  // Use a value which is never set so we don't actually discard anything.
425  enquire.set_collapse_key(99);
426  mymset = enquire.get_mset(0, 1, 0, &myfunctor);
427  TEST_EQUAL(mymset.size(), 1);
428  TEST_REL(mymset.get_matches_lower_bound(),>=,1);
429  TEST_REL(mymset.get_matches_lower_bound(),<=,3);
430  TEST_REL(mymset.get_matches_upper_bound(),>=,3);
431  TEST_REL(mymset.get_matches_upper_bound(),<=,6);
432  TEST_REL(mymset.get_matches_estimated(),>,0);
433  TEST_REL(mymset.get_matches_estimated(),<=,6);
440 
441  // Check that the bounds are appropriate if a percentage cutoff is in
442  // use. Set a 1% threshold so we don't actually discard anything.
444  enquire.set_cutoff(1);
445  mymset = enquire.get_mset(0, 1, 0, &myfunctor);
446  TEST_EQUAL(mymset.size(), 1);
447  TEST_REL(mymset.get_matches_lower_bound(),>=,1);
448  TEST_REL(mymset.get_matches_lower_bound(),<=,3);
449  TEST_REL(mymset.get_matches_upper_bound(),>=,3);
450  TEST_REL(mymset.get_matches_upper_bound(),<=,6);
451  TEST_REL(mymset.get_matches_estimated(),>,0);
452  TEST_REL(mymset.get_matches_estimated(),<=,6);
459 
460  // And now with both a collapse key and percentage cutoff.
461  enquire.set_collapse_key(99);
462  mymset = enquire.get_mset(0, 1, 0, &myfunctor);
463  TEST_EQUAL(mymset.size(), 1);
464  TEST_REL(mymset.get_matches_lower_bound(),>=,1);
465  TEST_REL(mymset.get_matches_lower_bound(),<=,3);
466  TEST_REL(mymset.get_matches_upper_bound(),>=,3);
467  TEST_REL(mymset.get_matches_upper_bound(),<=,6);
468  TEST_REL(mymset.get_matches_estimated(),>,0);
469  TEST_REL(mymset.get_matches_estimated(),<=,6);
476 }
477 
478 // Test Xapian::MatchDecider functor used as a match spy.
479 DEFINE_TESTCASE(matchdecider2, backend && !remote) {
480  Xapian::Database db(get_database("apitest_simpledata"));
481  Xapian::Enquire enquire(db);
482  enquire.set_query(Xapian::Query("this"));
483 
484  GrepMatchDecider myfunctor("This is");
485 
486  Xapian::MSet mymset = enquire.get_mset(0, 100, 0, NULL, &myfunctor);
487 
488  vector<bool> docid_checked(db.get_lastdocid());
489 
490  // Check that we get the expected number of matches, and that they
491  // satisfy the condition.
492  Xapian::MSetIterator i = mymset.begin();
493  TEST(i != mymset.end());
494  TEST_EQUAL(mymset.size(), 3);
495  for ( ; i != mymset.end(); ++i) {
496  const Xapian::Document doc(i.get_document());
497  TEST(myfunctor(doc));
498  docid_checked[*i] = true;
499  }
500 
501  // Check that the other documents don't satisfy the condition.
502  for (Xapian::docid did = 1; did < docid_checked.size(); ++did) {
503  if (!docid_checked[did]) {
504  TEST(!myfunctor(db.get_document(did)));
505  }
506  }
507 }
508 
509 // Regression test for lower bound using functor, sorting and collapsing.
510 DEFINE_TESTCASE(matchdecider3, backend && !remote) {
511  Xapian::Database db(get_database("etext"));
512  Xapian::Enquire enquire(db);
513  enquire.set_query(Xapian::Query(""));
514  enquire.set_collapse_key(12);
515  enquire.set_sort_by_value(11, true);
516 
517  GrepMatchDecider myfunctor("We produce");
518 
519  Xapian::MSet mset1 = enquire.get_mset(0, 2, 0, NULL, &myfunctor);
520  Xapian::MSet mset2 = enquire.get_mset(0, 1000, 0, NULL, &myfunctor);
521 
522  // mset2 should contain all the hits, so the statistics should be exact.
523  TEST_EQUAL(mset2.get_matches_estimated(), mset2.size());
526 
529 
530  // Check that the lower bound in mset1 is not greater than the known
531  // number of hits. This failed until revision 10811.
532  TEST_REL(mset1.get_matches_lower_bound(),<=,mset2.size());
533 
534  // Check that the bounds for mset1 make sense.
537  TEST_REL(mset1.size(),<=,mset1.get_matches_upper_bound());
538 
541 
542  // We ask for 2 or more results so the matcher checks all candidates and
543  // can see that there would be exactly one uncollapsed match.
544  //
545  // In 1.4.x the matcher was less clever and gave doccount or (doccount - 1)
546  // as the uncollapsed upper bound.
549 }
550 
551 // tests that mset iterators on msets compare correctly.
552 DEFINE_TESTCASE(msetiterator1, backend) {
553  Xapian::Enquire enquire(get_database("apitest_simpledata"));
554  enquire.set_query(Xapian::Query("this"));
555  Xapian::MSet mymset = enquire.get_mset(0, 2);
556 
558  j = mymset.begin();
559  Xapian::MSetIterator k = mymset.end();
562  Xapian::MSetIterator n = mymset.begin();
563  Xapian::MSetIterator o = mymset.begin();
564  TEST_NOT_EQUAL(j, k);
565  TEST_NOT_EQUAL(l, m);
566  TEST_EQUAL(k, m);
567  TEST_EQUAL(j, l);
568  TEST_EQUAL(j, j);
569  TEST_EQUAL(k, k);
570 
571  k = j;
572  TEST_EQUAL(j, k);
573  TEST_EQUAL(j, o);
574  k++;
575  TEST_NOT_EQUAL(j, k);
576  TEST_NOT_EQUAL(k, l);
577  TEST_NOT_EQUAL(k, m);
578  TEST_NOT_EQUAL(k, o);
579  o++;
580  TEST_EQUAL(k, o);
581  k++;
582  TEST_NOT_EQUAL(j, k);
583  TEST_NOT_EQUAL(k, l);
584  TEST_EQUAL(k, m);
585  TEST_EQUAL(n, l);
586 
587  n = m;
588  TEST_NOT_EQUAL(n, l);
589  TEST_EQUAL(n, m);
590  TEST_NOT_EQUAL(n, mymset.begin());
591  TEST_EQUAL(n, mymset.end());
592 }
593 
594 // tests that mset iterators on empty msets compare equal.
595 DEFINE_TESTCASE(msetiterator2, backend) {
596  Xapian::Enquire enquire(get_database("apitest_simpledata"));
597  enquire.set_query(Xapian::Query("this"));
598  Xapian::MSet mymset = enquire.get_mset(0, 0);
599 
600  Xapian::MSetIterator j = mymset.begin();
601  Xapian::MSetIterator k = mymset.end();
604  TEST_EQUAL(j, k);
605  TEST_EQUAL(l, m);
606  TEST_EQUAL(k, m);
607  TEST_EQUAL(j, l);
608  TEST_EQUAL(j, j);
609  TEST_EQUAL(k, k);
610 }
611 
612 // tests that begin().get_document() works when first != 0
613 DEFINE_TESTCASE(msetiterator3, backend) {
614  Xapian::Database mydb(get_database("apitest_simpledata"));
615  Xapian::Enquire enquire(mydb);
616  enquire.set_query(Xapian::Query("this"));
617 
618  Xapian::MSet mymset = enquire.get_mset(2, 10);
619 
620  TEST(!mymset.empty());
621  Xapian::Document doc(mymset.begin().get_document());
622  TEST(!doc.get_data().empty());
623 }
624 
625 // tests that eset iterators on empty esets compare equal.
626 DEFINE_TESTCASE(esetiterator1, backend) {
627  Xapian::Enquire enquire(get_database("apitest_simpledata"));
628  enquire.set_query(Xapian::Query("this"));
629 
630  Xapian::MSet mymset = enquire.get_mset(0, 10);
631  TEST(mymset.size() >= 2);
632 
633  Xapian::RSet myrset;
634  Xapian::MSetIterator i = mymset.begin();
635  myrset.add_document(*i);
636  myrset.add_document(*(++i));
637 
638  Xapian::ESet myeset = enquire.get_eset(2, myrset);
640  j = myeset.begin();
641  Xapian::ESetIterator k = myeset.end();
644  Xapian::ESetIterator n = myeset.begin();
645 
646  TEST_NOT_EQUAL(j, k);
647  TEST_NOT_EQUAL(l, m);
648  TEST_EQUAL(k, m);
649  TEST_EQUAL(j, l);
650  TEST_EQUAL(j, j);
651  TEST_EQUAL(k, k);
652 
653  k = j;
654  TEST_EQUAL(j, k);
655  k++;
656  TEST_NOT_EQUAL(j, k);
657  TEST_NOT_EQUAL(k, l);
658  TEST_NOT_EQUAL(k, m);
659  k++;
660  TEST_NOT_EQUAL(j, k);
661  TEST_NOT_EQUAL(k, l);
662  TEST_EQUAL(k, m);
663  TEST_EQUAL(n, l);
664 
665  n = m;
666  TEST_NOT_EQUAL(n, l);
667  TEST_EQUAL(n, m);
668  TEST_NOT_EQUAL(n, myeset.begin());
669  TEST_EQUAL(n, myeset.end());
670 }
671 
672 // tests that eset iterators on empty esets compare equal.
673 DEFINE_TESTCASE(esetiterator2, backend) {
674  Xapian::Enquire enquire(get_database("apitest_simpledata"));
675  enquire.set_query(Xapian::Query("this"));
676 
677  Xapian::MSet mymset = enquire.get_mset(0, 10);
678  TEST(mymset.size() >= 2);
679 
680  Xapian::RSet myrset;
681  Xapian::MSetIterator i = mymset.begin();
682  myrset.add_document(*i);
683  myrset.add_document(*(++i));
684 
685  Xapian::ESet myeset = enquire.get_eset(0, myrset);
686  Xapian::ESetIterator j = myeset.begin();
687  Xapian::ESetIterator k = myeset.end();
690  TEST_EQUAL(j, k);
691  TEST_EQUAL(l, m);
692  TEST_EQUAL(k, m);
693  TEST_EQUAL(j, l);
694  TEST_EQUAL(j, j);
695  TEST_EQUAL(k, k);
696 }
697 
698 // tests the collapse-on-key
699 DEFINE_TESTCASE(collapsekey1, backend) {
700  Xapian::Enquire enquire(get_database("apitest_simpledata"));
701  enquire.set_query(Xapian::Query("this"));
702 
703  Xapian::MSet mymset1 = enquire.get_mset(0, 100);
704  Xapian::doccount mymsize1 = mymset1.size();
705 
706  for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
707  enquire.set_collapse_key(value_no);
708  Xapian::MSet mymset = enquire.get_mset(0, 100);
709 
710  TEST_AND_EXPLAIN(mymsize1 > mymset.size(),
711  "Had no fewer items when performing collapse: don't know whether it worked.");
712 
713  map<string, Xapian::docid> values;
714  Xapian::MSetIterator i = mymset.begin();
715  for ( ; i != mymset.end(); ++i) {
716  string value = i.get_document().get_value(value_no);
717  TEST(values[value] == 0 || value.empty());
718  values[value] = *i;
719  }
720  }
721 }
722 
723 // tests that collapse-on-key modifies the predicted bounds for the number of
724 // matches appropriately.
725 DEFINE_TESTCASE(collapsekey2, multi && remote) {
726  // FIXME: This needs an appropriate database creating to work for other
727  // backend combinations - currently those return the same bound after
728  // collapsing (which is valid, but doesn't show the effect we are trying
729  // to test for here).
730  Xapian::Enquire enquire(get_database("apitest_simpledata2"));
731  enquire.set_query(Xapian::Query("this"));
732 
733  Xapian::MSet mset1 = enquire.get_mset(0, 1);
734 
735  // Test that if no duplicates are found, then the upper bound remains
736  // unchanged and the lower bound drops.
737  {
738  enquire.set_query(Xapian::Query("this"));
739  Xapian::valueno value_no = 3;
740  enquire.set_collapse_key(value_no);
741  Xapian::MSet mset = enquire.get_mset(0, 1);
742 
745  }
746 }
747 
748 // tests that collapse-on-key modifies the predicted bounds for the number of
749 // matches appropriately.
750 DEFINE_TESTCASE(collapsekey3, backend) {
751  Xapian::Enquire enquire(get_database("apitest_simpledata"));
752  enquire.set_query(Xapian::Query("this"));
753 
754  Xapian::MSet mymset1 = enquire.get_mset(0, 3);
755 
756  for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
757  enquire.set_collapse_key(value_no);
758  Xapian::MSet mymset = enquire.get_mset(0, 3);
759 
761  "Lower bound was not lower when performing collapse: don't know whether it worked.");
763  "Upper bound was not lower when performing collapse: don't know whether it worked.");
764 
765  map<string, Xapian::docid> values;
766  Xapian::MSetIterator i = mymset.begin();
767  for ( ; i != mymset.end(); ++i) {
768  string value = i.get_document().get_value(value_no);
769  TEST(values[value] == 0 || value.empty());
770  values[value] = *i;
771  }
772  }
773 
774  // Test that if the collapse value is always empty, then the upper bound
775  // remains unchanged, and the lower bound is the same or lower (it can be
776  // lower because the matcher counts the number of documents with empty
777  // collapse keys, but may have rejected a document because its weight is
778  // too low for the proto-MSet before it even looks at its collapse key).
779  {
780  Xapian::valueno value_no = 1000;
781  enquire.set_collapse_key(value_no);
782  Xapian::MSet mymset = enquire.get_mset(0, 3);
783 
786 
787  map<string, Xapian::docid> values;
788  Xapian::MSetIterator i = mymset.begin();
789  for ( ; i != mymset.end(); ++i) {
790  string value = i.get_document().get_value(value_no);
791  TEST(values[value] == 0 || value.empty());
792  values[value] = *i;
793  }
794  }
795 }
796 
797 // tests that collapse-on-key modifies the predicted bounds for the number of
798 // matches appropriately even when no results are requested.
799 DEFINE_TESTCASE(collapsekey4, backend) {
800  Xapian::Enquire enquire(get_database("apitest_simpledata"));
801  enquire.set_query(Xapian::Query("this"));
802 
803  Xapian::MSet mymset1 = enquire.get_mset(0, 0);
804 
805  for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
806  enquire.set_collapse_key(value_no);
807  Xapian::MSet mymset = enquire.get_mset(0, 0);
808 
810  "Lower bound was not 1 when performing collapse but not asking for any results.");
812  "Upper bound was changed when performing collapse but not asking for any results.");
813 
814  map<string, Xapian::docid> values;
815  Xapian::MSetIterator i = mymset.begin();
816  for ( ; i != mymset.end(); ++i) {
817  string value = i.get_document().get_value(value_no);
818  TEST(values[value] == 0 || value.empty());
819  values[value] = *i;
820  }
821  }
822 }
823 
824 // test for keepalives
825 DEFINE_TESTCASE(keepalive1, remote) {
826  Xapian::Database db(get_remote_database("apitest_simpledata", 5000));
827 
828  /* Test that keep-alives work */
829  for (int i = 0; i < 10; ++i) {
830  sleep(2);
831  db.keep_alive();
832  }
833  Xapian::Enquire enquire(db);
834  enquire.set_query(Xapian::Query("word"));
835  enquire.get_mset(0, 10);
836 
837  /* Test that things break without keepalives */
838  sleep(10);
839  enquire.set_query(Xapian::Query("word"));
840  /* Currently this can throw NetworkError or NetworkTimeoutError (which is
841  * a subclass of NetworkError).
842  */
844  enquire.get_mset(0, 10));
845 }
846 
847 // test that iterating through all terms in a database works.
848 DEFINE_TESTCASE(allterms1, backend) {
849  Xapian::Database db(get_database("apitest_allterms"));
851  TEST(ati != db.allterms_end());
852  TEST_EQUAL(*ati, "one");
853  TEST_EQUAL(ati.get_termfreq(), 1);
854 
855  ati++;
856  TEST(ati != db.allterms_end());
857  if (verbose) {
858  tout << "*ati = '" << *ati << "'\n";
859  tout << "*ati.length = '" << (*ati).length() << "'\n";
860  tout << "*ati == \"one\" = " << (*ati == "one") << "\n";
861  tout << "*ati[3] = " << ((*ati)[3]) << "\n";
862  tout << "*ati = '" << *ati << "'\n";
863  }
864  TEST(*ati == "three");
865  TEST(ati.get_termfreq() == 3);
866 
867  ++ati;
868  TEST(ati != db.allterms_end());
869  TEST(*ati == "two");
870  TEST(ati.get_termfreq() == 2);
871 
872  ati++;
873  TEST(ati == db.allterms_end());
874 }
875 
876 // test that iterating through all terms in two databases works.
877 DEFINE_TESTCASE(allterms2, backend) {
878  Xapian::Database db;
879  db.add_database(get_database("apitest_allterms"));
880  db.add_database(get_database("apitest_allterms2"));
882 
883  TEST(ati != db.allterms_end());
884  TEST(*ati == "five");
885  TEST(ati.get_termfreq() == 2);
886  ati++;
887 
888  TEST(ati != db.allterms_end());
889  TEST(*ati == "four");
890  TEST(ati.get_termfreq() == 1);
891 
892  ati++;
893  TEST(ati != db.allterms_end());
894  TEST(*ati == "one");
895  TEST(ati.get_termfreq() == 1);
896 
897  ++ati;
898  TEST(ati != db.allterms_end());
899  TEST(*ati == "six");
900  TEST(ati.get_termfreq() == 3);
901 
902  ati++;
903  TEST(ati != db.allterms_end());
904  TEST(*ati == "three");
905  TEST(ati.get_termfreq() == 3);
906 
907  ati++;
908  TEST(ati != db.allterms_end());
909  TEST(*ati == "two");
910  TEST(ati.get_termfreq() == 2);
911 
912  ati++;
913  TEST(ati == db.allterms_end());
914 }
915 
916 // test that skip_to sets at_end (regression test)
917 DEFINE_TESTCASE(allterms3, backend) {
918  Xapian::Database db;
919  db.add_database(get_database("apitest_allterms"));
921 
922  ati.skip_to(string("zzzzzz"));
923  TEST(ati == db.allterms_end());
924 }
925 
926 // test that next ignores extra entries due to long posting lists being
927 // chunked (regression test for quartz)
928 DEFINE_TESTCASE(allterms4, backend) {
929  // apitest_allterms4 contains 682 documents each containing just the word
930  // "foo". 682 was the magic number which started to cause Quartz problems.
931  Xapian::Database db = get_database("apitest_allterms4");
932 
934  TEST(i != db.allterms_end());
935  TEST(*i == "foo");
936  TEST(i.get_termfreq() == 682);
937  ++i;
938  TEST(i == db.allterms_end());
939 }
940 
941 // test that skip_to with an exact match sets the current term (regression test
942 // for quartz)
943 DEFINE_TESTCASE(allterms5, backend) {
944  Xapian::Database db;
945  db.add_database(get_database("apitest_allterms"));
947  ati.skip_to("three");
948  TEST(ati != db.allterms_end());
949  TEST_EQUAL(*ati, "three");
950 }
951 
952 // test allterms iterators with prefixes
953 DEFINE_TESTCASE(allterms6, backend) {
954  Xapian::Database db;
955  db.add_database(get_database("apitest_allterms"));
956  db.add_database(get_database("apitest_allterms2"));
957 
958  Xapian::TermIterator ati = db.allterms_begin("three");
959  TEST(ati != db.allterms_end("three"));
960  TEST_EQUAL(*ati, "three");
961  ati.skip_to("three");
962  TEST(ati != db.allterms_end("three"));
963  TEST_EQUAL(*ati, "three");
964  ati++;
965  TEST(ati == db.allterms_end("three"));
966 
967  ati = db.allterms_begin("thre");
968  TEST(ati != db.allterms_end("thre"));
969  TEST_EQUAL(*ati, "three");
970  ati.skip_to("three");
971  TEST(ati != db.allterms_end("thre"));
972  TEST_EQUAL(*ati, "three");
973  ati++;
974  TEST(ati == db.allterms_end("thre"));
975 
976  ati = db.allterms_begin("f");
977  TEST(ati != db.allterms_end("f"));
978  TEST_EQUAL(*ati, "five");
979  TEST(ati != db.allterms_end("f"));
980  ati.skip_to("three");
981  TEST(ati == db.allterms_end("f"));
982 
983  ati = db.allterms_begin("f");
984  TEST(ati != db.allterms_end("f"));
985  TEST_EQUAL(*ati, "five");
986  ati++;
987  TEST(ati != db.allterms_end("f"));
988  TEST_EQUAL(*ati, "four");
989  ati++;
990  TEST(ati == db.allterms_end("f"));
991 
992  ati = db.allterms_begin("absent");
993  TEST(ati == db.allterms_end("absent"));
994 }
995 
996 // test that searching for a term with a special characters in it works
997 DEFINE_TESTCASE(specialterms1, backend) {
998  Xapian::Enquire enquire(get_database("apitest_space"));
999  Xapian::MSet mymset;
1000  Xapian::doccount count;
1002  Xapian::Stem stemmer("english");
1003 
1004  enquire.set_query(stemmer("new\nline"));
1005  mymset = enquire.get_mset(0, 10);
1006  TEST_MSET_SIZE(mymset, 1);
1007  count = 0;
1008  for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
1009  TEST_EQUAL(count, 1);
1010 
1011  for (Xapian::valueno value_no = 0; value_no < 7; ++value_no) {
1012  string value = mymset.begin().get_document().get_value(value_no);
1013  TEST_NOT_EQUAL(value, "");
1014  if (value_no == 0) {
1015  TEST(value.size() > 263);
1016  TEST_EQUAL(static_cast<unsigned char>(value[262]), 255);
1017  for (int k = 0; k < 256; ++k) {
1018  TEST_EQUAL(static_cast<unsigned char>(value[k + 7]), k);
1019  }
1020  }
1021  }
1022 
1023  enquire.set_query(stemmer(string("big\0zero", 8)));
1024  mymset = enquire.get_mset(0, 10);
1025  TEST_MSET_SIZE(mymset, 1);
1026  count = 0;
1027  for (m = mymset.begin(); m != mymset.end(); ++m) ++count;
1028  TEST_EQUAL(count, 1);
1029 }
1030 
1031 // test that terms with a special characters in appear correctly when iterating
1032 // allterms
1033 DEFINE_TESTCASE(specialterms2, backend) {
1034  Xapian::Database db(get_database("apitest_space"));
1035 
1036  // Check the terms are all as expected (after stemming) and that allterms
1037  // copes with iterating over them.
1039  t = db.allterms_begin();
1040  TEST_EQUAL(*t, "back\\slash"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1041  TEST_EQUAL(*t, string("big\0zero", 8)); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1042  TEST_EQUAL(*t, "new\nlin"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1043  TEST_EQUAL(*t, "one\x01on"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1044  TEST_EQUAL(*t, "space man"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1045  TEST_EQUAL(*t, "tab\tbi"); ++t; TEST_NOT_EQUAL(t, db.allterms_end());
1046  TEST_EQUAL(*t, "tu\x02tu"); ++t; TEST_EQUAL(t, db.allterms_end());
1047 
1048  // Now check that skip_to exactly a term containing a zero byte works.
1049  // This is a regression test for flint and quartz - an Assert() used to
1050  // fire in debug builds (the Assert was wrong - the actual code handled
1051  // this OK).
1052  t = db.allterms_begin();
1053  t.skip_to(string("big\0zero", 8));
1054  TEST_NOT_EQUAL(t, db.allterms_end());
1055  TEST_EQUAL(*t, string("big\0zero", 8));
1056 }
1057 
1058 // test that rsets behave correctly with multiDBs
1059 DEFINE_TESTCASE(rsetmultidb2, backend && !multi) {
1060  Xapian::Database mydb1(get_database("apitest_rset", "apitest_simpledata2"));
1061  Xapian::Database mydb2(get_database("apitest_rset"));
1062  mydb2.add_database(get_database("apitest_simpledata2"));
1063 
1064  Xapian::Enquire enquire1(mydb1);
1065  Xapian::Enquire enquire2(mydb2);
1066 
1067  Xapian::Query myquery = query("is");
1068 
1069  enquire1.set_query(myquery);
1070  enquire2.set_query(myquery);
1071 
1072  Xapian::RSet myrset1;
1073  Xapian::RSet myrset2;
1074  myrset1.add_document(4);
1075  myrset2.add_document(2);
1076 
1077  Xapian::MSet mymset1a = enquire1.get_mset(0, 10);
1078  Xapian::MSet mymset1b = enquire1.get_mset(0, 10, &myrset1);
1079  Xapian::MSet mymset2a = enquire2.get_mset(0, 10);
1080  Xapian::MSet mymset2b = enquire2.get_mset(0, 10, &myrset2);
1081 
1082  mset_expect_order(mymset1a, 4, 3);
1083  mset_expect_order(mymset1b, 4, 3);
1084  mset_expect_order(mymset2a, 2, 5);
1085  mset_expect_order(mymset2b, 2, 5);
1086 
1087  TEST(mset_range_is_same_weights(mymset1a, 0, mymset2a, 0, 2));
1088  TEST(mset_range_is_same_weights(mymset1b, 0, mymset2b, 0, 2));
1089  TEST_NOT_EQUAL(mymset1a, mymset1b);
1090  TEST_NOT_EQUAL(mymset2a, mymset2b);
1091 }
1092 
1093 // tests an expand across multiple databases
1094 DEFINE_TESTCASE(multiexpand1, backend && !multi) {
1095  Xapian::Database mydb1(get_database("apitest_simpledata", "apitest_simpledata2"));
1096  Xapian::Enquire enquire1(mydb1);
1097 
1098  Xapian::Database mydb2(get_database("apitest_simpledata"));
1099  mydb2.add_database(get_database("apitest_simpledata2"));
1100  Xapian::Enquire enquire2(mydb2);
1101 
1102  // make simple equivalent rsets, with a document from each database in each.
1103  Xapian::RSet rset1;
1104  Xapian::RSet rset2;
1105  rset1.add_document(1);
1106  rset1.add_document(7);
1107  rset2.add_document(1);
1108  rset2.add_document(2);
1109 
1110  // Retrieve all the ESet results in each of the three setups:
1111 
1112  // This is the single database one.
1113  Xapian::ESet eset1 = enquire1.get_eset(1000, rset1);
1114 
1115  // This is the multi database with approximation
1116  Xapian::ESet eset2 = enquire2.get_eset(1000, rset2);
1117 
1118  // This is the multi database without approximation
1119  Xapian::ESet eset3 = enquire2.get_eset(1000, rset2, Xapian::Enquire::USE_EXACT_TERMFREQ);
1120 
1121  TEST_EQUAL(eset1.size(), eset3.size());
1122 
1123  Xapian::ESetIterator i = eset1.begin();
1124  Xapian::ESetIterator j = eset3.begin();
1125  while (i != eset1.end() && j != eset3.end()) {
1126  TEST_EQUAL(*i, *j);
1127  TEST_EQUAL(i.get_weight(), j.get_weight());
1128  ++i;
1129  ++j;
1130  }
1131  TEST(i == eset1.end());
1132  TEST(j == eset3.end());
1133 
1134  bool eset1_eq_eset2 = true;
1135  i = eset1.begin();
1136  j = eset2.begin();
1137  while (i != eset1.end() && j != eset2.end()) {
1138  if (i.get_weight() != j.get_weight()) {
1139  eset1_eq_eset2 = false;
1140  break;
1141  }
1142  ++i;
1143  ++j;
1144  }
1145  TEST(!eset1_eq_eset2);
1146 }
1147 
1148 // tests that opening a non-existent postlist returns an empty list
1149 DEFINE_TESTCASE(postlist1, backend) {
1150  Xapian::Database db(get_database("apitest_simpledata"));
1151 
1152  TEST_EQUAL(db.postlist_begin("rosebud"), db.postlist_end("rosebud"));
1153 
1154  string s = "let_us_see_if_we_can_break_it_with_a_really_really_long_term.";
1155  for (int i = 0; i < 8; ++i) {
1156  s += s;
1157  TEST_EQUAL(db.postlist_begin(s), db.postlist_end(s));
1158  }
1159 
1160  // A regression test (no, really!)
1161  TEST_NOT_EQUAL(db.postlist_begin("a"), db.postlist_end("a"));
1162 }
1163 
1164 // tests that a Xapian::PostingIterator works as an STL iterator
1165 DEFINE_TESTCASE(postlist2, backend) {
1166  Xapian::Database db(get_database("apitest_simpledata"));
1168  p = db.postlist_begin("this");
1169  Xapian::PostingIterator pend = db.postlist_end("this");
1170 
1171  TEST(p.get_description() != "PostingIterator()");
1172 
1173  // test operator= creates a copy which compares equal
1174  Xapian::PostingIterator p_copy = p;
1175  TEST_EQUAL(p, p_copy);
1176 
1177  TEST(p_copy.get_description() != "PostingIterator()");
1178 
1179  // test copy constructor creates a copy which compares equal
1180  Xapian::PostingIterator p_clone(p);
1181  TEST_EQUAL(p, p_clone);
1182 
1183  TEST(p_clone.get_description() != "PostingIterator()");
1184 
1185  vector<Xapian::docid> v(p, pend);
1186 
1187  p = db.postlist_begin("this");
1188  pend = db.postlist_end("this");
1189  vector<Xapian::docid>::const_iterator i;
1190  for (i = v.begin(); i != v.end(); ++i) {
1191  TEST_NOT_EQUAL(p, pend);
1192  TEST_EQUAL(*i, *p);
1193  p++;
1194  }
1195  TEST_EQUAL(p, pend);
1196 
1197  TEST_STRINGS_EQUAL(p.get_description(), "PostingIterator()");
1198  TEST_STRINGS_EQUAL(pend.get_description(), "PostingIterator()");
1199 }
1200 
1201 // tests that a Xapian::PostingIterator still works when the DB is deleted
1202 DEFINE_TESTCASE(postlist3, backend) {
1204  {
1205  Xapian::Database db_temp(get_database("apitest_simpledata"));
1206  u = db_temp.postlist_begin("this");
1207  }
1208 
1209  Xapian::Database db(get_database("apitest_simpledata"));
1211  Xapian::PostingIterator pend = db.postlist_end("this");
1212 
1213  while (p != pend) {
1214  TEST_EQUAL(*p, *u);
1215  p++;
1216  u++;
1217  }
1218 }
1219 
1220 // tests skip_to
1221 DEFINE_TESTCASE(postlist4, backend) {
1222  Xapian::Database db(get_database("apitest_simpledata"));
1223  Xapian::PostingIterator i = db.postlist_begin("this");
1224  i.skip_to(1);
1225  i.skip_to(999999999);
1226  TEST(i == db.postlist_end("this"));
1227 }
1228 
1229 // tests long postlists
1230 DEFINE_TESTCASE(postlist5, backend) {
1231  Xapian::Database db(get_database("apitest_manydocs"));
1233  Xapian::PostingIterator i = db.postlist_begin("this");
1234  unsigned int j = 1;
1235  while (i != db.postlist_end("this")) {
1236  TEST_EQUAL(*i, j);
1237  i++;
1238  j++;
1239  }
1240  TEST_EQUAL(j, 513);
1241 }
1242 
1243 // tests document length in postlists
1244 DEFINE_TESTCASE(postlist6, backend) {
1245  Xapian::Database db(get_database("apitest_simpledata"));
1246  Xapian::PostingIterator i = db.postlist_begin("this");
1247  TEST(i != db.postlist_end("this"));
1248  while (i != db.postlist_end("this")) {
1249  TEST_EQUAL(i.get_doclength(), db.get_doclength(*i));
1251  TEST_REL(i.get_wdf(),<=,i.get_doclength());
1252  TEST_REL(1,<=,i.get_unique_terms());
1253  // The next two aren't necessarily true if there are terms with wdf=0
1254  // in the document, but that isn't the case here.
1256  TEST_REL(i.get_wdf() + i.get_unique_terms() - 1,<=,i.get_doclength());
1257  ++i;
1258  }
1259 }
1260 
1261 // tests collection frequency
1262 DEFINE_TESTCASE(collfreq1, backend) {
1263  Xapian::Database db(get_database("apitest_simpledata"));
1264 
1265  TEST_EQUAL(db.get_collection_freq("this"), 11);
1266  TEST_EQUAL(db.get_collection_freq("first"), 1);
1267  TEST_EQUAL(db.get_collection_freq("last"), 0);
1268  TEST_EQUAL(db.get_collection_freq("word"), 9);
1269 
1270  Xapian::Database db1(get_database("apitest_simpledata", "apitest_simpledata2"));
1271  Xapian::Database db2(get_database("apitest_simpledata"));
1272  db2.add_database(get_database("apitest_simpledata2"));
1273 
1274  TEST_EQUAL(db1.get_collection_freq("this"), 15);
1275  TEST_EQUAL(db1.get_collection_freq("first"), 1);
1276  TEST_EQUAL(db1.get_collection_freq("last"), 0);
1277  TEST_EQUAL(db1.get_collection_freq("word"), 11);
1278  TEST_EQUAL(db2.get_collection_freq("this"), 15);
1279  TEST_EQUAL(db2.get_collection_freq("first"), 1);
1280  TEST_EQUAL(db2.get_collection_freq("last"), 0);
1281  TEST_EQUAL(db2.get_collection_freq("word"), 11);
1282 }
1283 
1284 // Regression test for split msets being incorrect when sorting
1285 DEFINE_TESTCASE(sortvalue1, backend) {
1286  Xapian::Enquire enquire(get_database("apitest_simpledata"));
1287  enquire.set_query(Xapian::Query("this"));
1288 
1289  for (int pass = 1; pass <= 2; ++pass) {
1290  for (Xapian::valueno value_no = 1; value_no < 7; ++value_no) {
1291  tout << "Sorting on value " << value_no << '\n';
1292  enquire.set_sort_by_value(value_no, true);
1293  Xapian::MSet allbset = enquire.get_mset(0, 100);
1294  Xapian::MSet partbset1 = enquire.get_mset(0, 3);
1295  Xapian::MSet partbset2 = enquire.get_mset(3, 97);
1296  TEST_EQUAL(allbset.size(), partbset1.size() + partbset2.size());
1297 
1298  bool ok = true;
1299  int n = 0;
1300  Xapian::MSetIterator i, j;
1301  j = allbset.begin();
1302  for (i = partbset1.begin(); i != partbset1.end(); ++i) {
1303  tout << "Entry " << n << ": " << *i << " | " << *j << '\n';
1304  TEST(j != allbset.end());
1305  if (*i != *j) ok = false;
1306  ++j;
1307  ++n;
1308  }
1309  tout << "===\n";
1310  for (i = partbset2.begin(); i != partbset2.end(); ++i) {
1311  tout << "Entry " << n << ": " << *i << " | " << *j << '\n';
1312  TEST(j != allbset.end());
1313  if (*i != *j) ok = false;
1314  ++j;
1315  ++n;
1316  }
1317  TEST(j == allbset.end());
1318  if (!ok)
1319  FAIL_TEST("Split msets aren't consistent with unsplit");
1320  }
1322  }
1323 }
1324 
1325 // consistency check match - vary mset size and check results agree.
1326 // consistency1 will run on the remote backend, but it's particularly slow
1327 // with that, and testing it there doesn't actually improve the test
1328 // coverage really.
1329 DEFINE_TESTCASE(consistency1, backend && !remote) {
1330  Xapian::Database db(get_database("etext"));
1331  Xapian::Enquire enquire(db);
1333  Xapian::doccount lots = 214;
1334  Xapian::MSet bigmset = enquire.get_mset(0, lots);
1335  TEST_EQUAL(bigmset.size(), lots);
1336  try {
1337  for (Xapian::doccount start = 0; start < lots; ++start) {
1338  for (Xapian::doccount size = 0; size < lots - start; ++size) {
1339  Xapian::MSet mset = enquire.get_mset(start, size);
1340  if (mset.size()) {
1341  TEST_EQUAL(start + mset.size(),
1342  min(start + size, bigmset.size()));
1343  } else if (size) {
1344 // tout << start << mset.size() << bigmset.size() << '\n';
1345  TEST(start >= bigmset.size());
1346  }
1347  for (Xapian::doccount i = 0; i < mset.size(); ++i) {
1348  TEST_EQUAL(*mset[i], *bigmset[start + i]);
1349  TEST_EQUAL_DOUBLE(mset[i].get_weight(),
1350  bigmset[start + i].get_weight());
1351  }
1352  }
1353  }
1354  } catch (const Xapian::NetworkTimeoutError &) {
1355  // consistency1 is a long test - may timeout with the remote backend...
1356  SKIP_TEST("Test taking too long");
1357  }
1358 }
1359 
1360 // Test that specifying a nonexistent input file throws an exception
1361 // (backend-specific cases).
1362 DEFINE_TESTCASE(databasenotfounderror1, glass || honey) {
1363  string db_dir = "." + get_dbtype();
1364 
1365  int db_type_flag;
1366  if (get_dbtype() == "glass") {
1367  db_type_flag = Xapian::DB_BACKEND_GLASS;
1368  } else if (get_dbtype() == "honey") {
1369  db_type_flag = Xapian::DB_BACKEND_HONEY;
1370  } else {
1371  FAIL_TEST("Backend " + get_dbtype() + " not handled by testcase");
1372  }
1373 
1374  mkdir(db_dir.c_str(), 0755);
1375  db_dir += '/';
1376 
1378  Xapian::Database(db_dir + "nosuchdirectory", db_type_flag));
1379  if (db_type_flag != Xapian::DB_BACKEND_HONEY) {
1381  Xapian::WritableDatabase(db_dir + "nosuchdirectory",
1382  db_type_flag | Xapian::DB_OPEN));
1383  }
1384 
1385  string empty_dir = db_dir + "emptydirectory";
1386  mkdir(empty_dir.c_str(), 0700);
1388  Xapian::Database(empty_dir, db_type_flag));
1389 
1390  string some_file = db_dir + "somefile";
1391  touch(some_file);
1393  Xapian::Database(some_file, db_type_flag));
1394  if (db_type_flag != Xapian::DB_BACKEND_HONEY) {
1396  Xapian::WritableDatabase(some_file,
1397  db_type_flag | Xapian::DB_OPEN));
1399  Xapian::WritableDatabase(some_file,
1400  db_type_flag | Xapian::DB_CREATE));
1402  Xapian::WritableDatabase(some_file,
1403  db_type_flag | Xapian::DB_CREATE_OR_OPEN));
1405  Xapian::WritableDatabase(some_file,
1406  db_type_flag | Xapian::DB_CREATE_OR_OVERWRITE));
1407  }
1408 }
1409 
1410 // Test that specifying a nonexistent input file throws an exception
1411 // (non-backend-specific cases).
1412 DEFINE_TESTCASE(databasenotfounderror2, !backend) {
1414  Xapian::Database("nosuchdirectory"));
1416  Xapian::Database("no/such/directory"));
1417 
1419  Xapian::WritableDatabase("nosuchdirectory", Xapian::DB_OPEN));
1421  Xapian::WritableDatabase("no/such/directory", Xapian::DB_OPEN));
1422 
1423  string empty_dir = "emptydirectory";
1424  mkdir(empty_dir.c_str(), 0700);
1426  Xapian::Database{empty_dir});
1427 }
1428 
1430 DEFINE_TESTCASE(glassdatabaseopen1, glass) {
1431 #ifdef XAPIAN_HAS_GLASS_BACKEND
1432  const string dbdir = ".glass/test_glassdatabaseopen1";
1433  mkdir(".glass", 0755);
1434 
1435  {
1436  rm_rf(dbdir);
1437  Xapian::WritableDatabase wdb(dbdir,
1443  }
1444 
1445  {
1446  rm_rf(dbdir);
1447  Xapian::WritableDatabase wdb(dbdir,
1453  }
1454 
1455  {
1456  rm_rf(dbdir);
1457  Xapian::WritableDatabase wdb(dbdir,
1463  }
1464 
1465  {
1469  Xapian::WritableDatabase wdb(dbdir,
1472  }
1473 
1474  {
1475  Xapian::WritableDatabase wdb(dbdir,
1478  }
1479 
1480  {
1481  Xapian::WritableDatabase wdb(dbdir,
1484  }
1485 #endif
1486 }
1487 
1488 // feature test for Enquire:
1489 // set_sort_by_value
1490 // set_sort_by_value_then_relevance
1491 // set_sort_by_relevance_then_value
1492 // Prior to 1.2.17 and 1.3.2, order8 and order9 were swapped, and
1493 // set_sort_by_relevance_then_value was buggy, so this testcase now serves as
1494 // a regression test for that bug.
1495 DEFINE_TESTCASE(sortrel1, backend) {
1496  Xapian::Enquire enquire(get_database("apitest_sortrel"));
1497  enquire.set_sort_by_value(1, true);
1498  enquire.set_query(Xapian::Query("woman"));
1499 
1500  static const Xapian::docid order1[] = { 1,2,3,4,5,6,7,8,9 };
1501  static const Xapian::docid order2[] = { 2,1,3,6,5,4,7,9,8 };
1502  static const Xapian::docid order3[] = { 3,2,1,6,5,4,9,8,7 };
1503  static const Xapian::docid order4[] = { 7,8,9,4,5,6,1,2,3 };
1504  static const Xapian::docid order5[] = { 9,8,7,6,5,4,3,2,1 };
1505  static const Xapian::docid order6[] = { 7,9,8,6,5,4,2,1,3 };
1506  static const Xapian::docid order7[] = { 7,9,8,6,5,4,2,1,3 };
1507  static const Xapian::docid order8[] = { 2,6,7,1,5,9,3,4,8 };
1508  static const Xapian::docid order9[] = { 7,6,2,9,5,1,8,4,3 };
1509 
1510  Xapian::MSet mset;
1511  Xapian::doccount i;
1512 
1513  mset = enquire.get_mset(0, 10);
1514  TEST_EQUAL(mset.size(), sizeof(order1) / sizeof(Xapian::docid));
1515  for (i = 0; i < sizeof(order1) / sizeof(Xapian::docid); ++i) {
1516  TEST_EQUAL(*mset[i], order1[i]);
1517  }
1518 
1519  enquire.set_sort_by_value_then_relevance(1, true);
1520 
1521  mset = enquire.get_mset(0, 10);
1522  TEST_EQUAL(mset.size(), sizeof(order2) / sizeof(Xapian::docid));
1523  for (i = 0; i < sizeof(order2) / sizeof(Xapian::docid); ++i) {
1524  TEST_EQUAL(*mset[i], order2[i]);
1525  }
1526 
1527  enquire.set_sort_by_value(1, true);
1528 
1529  mset = enquire.get_mset(0, 10);
1530  TEST_EQUAL(mset.size(), sizeof(order1) / sizeof(Xapian::docid));
1531  for (i = 0; i < sizeof(order1) / sizeof(Xapian::docid); ++i) {
1532  TEST_EQUAL(*mset[i], order1[i]);
1533  }
1534 
1535  enquire.set_sort_by_value_then_relevance(1, true);
1537 
1538  mset = enquire.get_mset(0, 10);
1539  TEST_EQUAL(mset.size(), sizeof(order2) / sizeof(Xapian::docid));
1540  for (i = 0; i < sizeof(order2) / sizeof(Xapian::docid); ++i) {
1541  TEST_EQUAL(*mset[i], order2[i]);
1542  }
1543 
1544  enquire.set_sort_by_value(1, true);
1546 
1547  mset = enquire.get_mset(0, 10);
1548  TEST_EQUAL(mset.size(), sizeof(order3) / sizeof(Xapian::docid));
1549  for (i = 0; i < sizeof(order3) / sizeof(Xapian::docid); ++i) {
1550  TEST_EQUAL(*mset[i], order3[i]);
1551  }
1552 
1553  enquire.set_sort_by_value(1, false);
1555  mset = enquire.get_mset(0, 10);
1556  TEST_EQUAL(mset.size(), sizeof(order4) / sizeof(Xapian::docid));
1557  for (i = 0; i < sizeof(order4) / sizeof(Xapian::docid); ++i) {
1558  TEST_EQUAL(*mset[i], order4[i]);
1559  }
1560 
1561  enquire.set_sort_by_value(1, false);
1563  mset = enquire.get_mset(0, 10);
1564  TEST_EQUAL(mset.size(), sizeof(order5) / sizeof(Xapian::docid));
1565  for (i = 0; i < sizeof(order5) / sizeof(Xapian::docid); ++i) {
1566  TEST_EQUAL(*mset[i], order5[i]);
1567  }
1568 
1569  enquire.set_sort_by_value_then_relevance(1, false);
1571  mset = enquire.get_mset(0, 10);
1572  TEST_EQUAL(mset.size(), sizeof(order6) / sizeof(Xapian::docid));
1573  for (i = 0; i < sizeof(order6) / sizeof(Xapian::docid); ++i) {
1574  TEST_EQUAL(*mset[i], order6[i]);
1575  }
1576 
1577  enquire.set_sort_by_value_then_relevance(1, false);
1579  mset = enquire.get_mset(0, 10);
1580  TEST_EQUAL(mset.size(), sizeof(order7) / sizeof(Xapian::docid));
1581  for (i = 0; i < sizeof(order7) / sizeof(Xapian::docid); ++i) {
1582  TEST_EQUAL(*mset[i], order7[i]);
1583  }
1584 
1585  enquire.set_sort_by_relevance_then_value(1, true);
1587  mset = enquire.get_mset(0, 10);
1588  TEST_EQUAL(mset.size(), sizeof(order8) / sizeof(Xapian::docid));
1589  for (i = 0; i < sizeof(order8) / sizeof(Xapian::docid); ++i) {
1590  TEST_EQUAL(*mset[i], order8[i]);
1591  }
1592 
1593  enquire.set_sort_by_relevance_then_value(1, true);
1595  mset = enquire.get_mset(0, 10);
1596  TEST_EQUAL(mset.size(), sizeof(order8) / sizeof(Xapian::docid));
1597  for (i = 0; i < sizeof(order8) / sizeof(Xapian::docid); ++i) {
1598  TEST_EQUAL(*mset[i], order8[i]);
1599  }
1600 
1601  enquire.set_sort_by_relevance_then_value(1, false);
1603  mset = enquire.get_mset(0, 10);
1604  TEST_EQUAL(mset.size(), sizeof(order9) / sizeof(Xapian::docid));
1605  for (i = 0; i < sizeof(order9) / sizeof(Xapian::docid); ++i) {
1606  TEST_EQUAL(*mset[i], order9[i]);
1607  }
1608 
1609  enquire.set_sort_by_relevance_then_value(1, false);
1611  mset = enquire.get_mset(0, 10);
1612  TEST_EQUAL(mset.size(), sizeof(order9) / sizeof(Xapian::docid));
1613  for (i = 0; i < sizeof(order9) / sizeof(Xapian::docid); ++i) {
1614  TEST_EQUAL(*mset[i], order9[i]);
1615  }
1616 }
1617 
1618 static void
1620 {
1621  static const struct { Xapian::docid did; const char* text; } content[] = {
1622  {1, "This is a test document used with the API test. This paragraph "
1623  "must be at least three lines (including the blank line) to be "
1624  "counted as a \"paragraph\"."},
1625  {2, "This is a second simple data test, used to test multiple "
1626  "(inmemory anyway) databases. The text in this file is "
1627  "unimportant, although I suppose it ought to include the "
1628  "standard word \"word\" in a few places."},
1629  {3, "This file will be indexed by paragraph, and the simple query will "
1630  "search for the word \"word\". Well expect the mset to contain "
1631  "two documents, including this paragraph and the fourth, below. "
1632  "Since this paragraph uses the word \"word\" so much, this "
1633  "should be the first one in the match set. Ill just say the word "
1634  "a few more times (word!) to make sure of that. If this doesnt "
1635  "word (typo, I meant work), then there may be fourletter words "
1636  "spoken."},
1637  {4, "Ill leave this at two paragraphs. This one hasnt got any useful "
1638  "information in it either."},
1639  {5, "This paragraph only has a load of absolute rubbish, and nothing "
1640  "of any use whatsoever."},
1641  {7, "This is the other paragraph with the word in the simple query "
1642  "in it. For simplicity, all paragraphs are at least two lines, "
1643  "due to how the hacked up indexer works."},
1644  {9, "This is another paragraph which wont be returned. Well, not "
1645  "with the simple query, anyway."},
1646  {11, "And yet another. This one does mention banana splits, though, "
1647  "so cant be that bad."}
1648  };
1649 
1650  Xapian::TermGenerator indexer;
1651  indexer.set_stemmer(Xapian::Stem("english"));
1652  indexer.set_stemming_strategy(indexer.STEM_ALL);
1653 
1654  for (auto& i : content) {
1655  Xapian::Document doc;
1656  indexer.set_document(doc);
1657  indexer.index_text(i.text);
1658  db.replace_document(i.did, doc);
1659  }
1660 
1661  db.commit();
1662 }
1663 
1664 // Test network stats and local stats give the same results.
1665 DEFINE_TESTCASE(netstats1, backend) {
1666  static const char * const words[] = { "paragraph", "word" };
1667  Xapian::Query query(Xapian::Query::OP_OR, words, words + 2);
1668  const size_t MSET_SIZE = 10;
1669 
1670  Xapian::RSet rset;
1671  rset.add_document(4);
1672  rset.add_document(9);
1673 
1674  {
1675  Xapian::Database db = get_database("netstats1", make_netstats1_db);
1676 
1677  Xapian::Enquire enq(db);
1678  enq.set_query(query);
1679  Xapian::MSet mset = enq.get_mset(0, MSET_SIZE, &rset);
1682  TEST_EQUAL(mset.get_matches_estimated(), 7);
1683  TEST_EQUAL_DOUBLE(mset.get_max_attained(), 1.445962071042388164);
1684  TEST_EQUAL(mset.size(), 7);
1685 
1686  static const pair<Xapian::docid, double> to_compare[] = {
1687  {7, 1.445962071042388164},
1688  {3, 1.4140112748017070743},
1689  {1, 1.3747698831232337824},
1690  {5, 1.1654938419498412916},
1691  {9, 1.1654938419498412916},
1692  {4, 1.1543806706320836053},
1693  {2, 0.12268031290495594321}
1694  };
1695 
1696  TEST(mset_range_is_same(mset, 0, to_compare, mset.size()));
1697  }
1698 }
1699 
1700 // Coordinate matching - scores 1 for each matching term
1701 class MyWeight : public Xapian::Weight {
1703 
1704  public:
1705  MyWeight* clone() const override {
1706  return new MyWeight;
1707  }
1708  void init(double factor) override {
1709  scale_factor = factor;
1710  }
1711  MyWeight() { }
1713  std::string name() const override { return "MyWeight"; }
1714  string serialise() const override { return string(); }
1715  MyWeight* unserialise(const string&) const override {
1716  return new MyWeight;
1717  }
1721  Xapian::termcount) const override {
1722  return scale_factor;
1723  }
1724  double get_maxpart() const override { return scale_factor; }
1725 };
1726 
1727 // tests user weighting scheme.
1728 // Would work with remote if we registered the weighting scheme.
1729 // FIXME: do this so we also test that functionality...
1730 DEFINE_TESTCASE(userweight1, backend && !remote) {
1731  Xapian::Enquire enquire(get_database("apitest_simpledata"));
1732  enquire.set_weighting_scheme(MyWeight());
1733  static const char * const query[] = {
1734  "this", "line", "paragraph", "rubbish"
1735  };
1737  std::begin(query), std::end(query)));
1738  Xapian::MSet mymset1 = enquire.get_mset(0, 100);
1739  // MyWeight scores 1 for each matching term, so the weight should equal
1740  // the number of matching terms.
1741  for (Xapian::MSetIterator i = mymset1.begin(); i != mymset1.end(); ++i) {
1742  Xapian::termcount matching_terms = 0;
1744  while (t != enquire.get_matching_terms_end(i)) {
1745  ++matching_terms;
1746  ++t;
1747  }
1748  TEST_EQUAL(i.get_weight(), matching_terms);
1749  }
1750 }
1751 
1752 // tests MatchAll queries
1753 // This is a regression test, which failed with assertion failures in
1754 // revision 9094. Also check that the results aren't ranked by relevance
1755 // (regression test for bug fixed in 1.0.9).
1756 DEFINE_TESTCASE(matchall1, backend) {
1757  Xapian::Database db(get_database("apitest_simpledata"));
1758  Xapian::Enquire enquire(db);
1760  Xapian::MSet mset = enquire.get_mset(0, 10);
1763 
1765  Xapian::Query("nosuchterm"),
1767  mset = enquire.get_mset(0, 10);
1770 
1771  // Check that the results aren't ranked by relevance (fixed in 1.0.9).
1772  TEST(mset.size() > 1);
1773  TEST_EQUAL(mset[mset.size() - 1].get_weight(), 0);
1774  TEST_EQUAL(*mset[0], 1);
1775  TEST_EQUAL(*mset[mset.size() - 1], mset.size());
1776 }
1777 
1778 // Test using a ValueSetMatchDecider
1779 DEFINE_TESTCASE(valuesetmatchdecider2, backend && !remote) {
1780  Xapian::Database db(get_database("apitest_phrase"));
1781  Xapian::Enquire enq(db);
1782  enq.set_query(Xapian::Query("leav"));
1783 
1784  Xapian::ValueSetMatchDecider vsmd1(1, true);
1785  vsmd1.add_value("n");
1786  Xapian::ValueSetMatchDecider vsmd2(1, false);
1787  vsmd2.add_value("n");
1788 
1789  Xapian::MSet mymset = enq.get_mset(0, 20);
1790  mset_expect_order(mymset, 8, 6, 4, 5, 7, 10, 12, 11, 13, 9, 14);
1791  mymset = enq.get_mset(0, 20, 0, NULL, &vsmd1);
1792  mset_expect_order(mymset, 6, 12);
1793  mymset = enq.get_mset(0, 20, 0, NULL, &vsmd2);
1794  mset_expect_order(mymset, 8, 4, 5, 7, 10, 11, 13, 9, 14);
1795 }
static void make_netstats1_db(Xapian::WritableDatabase &db, const string &)
Definition: api_db.cc:1619
static Xapian::Query query(const string &t)
Definition: api_db.cc:49
#define EXPECTED_EXCEPTION
DEFINE_TESTCASE(termstats, backend)
Definition: api_db.cc:58
std::string get_dbtype()
Definition: apitest.cc:41
string get_database_path(const string &dbname)
Definition: apitest.cc:71
Xapian::Database get_database(const string &dbname)
Definition: apitest.cc:47
Xapian::Database get_remote_database(const string &dbname, unsigned int timeout, int *port_ptr)
Definition: apitest.cc:110
test functionality of the Xapian API
Base class for backend handling in test harness.
static const char * get_xapian_progsrv_command()
Get the command line required to run xapian-progsrv.
GrepMatchDecider(const string &needle_)
Definition: api_db.cc:342
string needle
Definition: api_db.cc:340
bool operator()(const Xapian::Document &doc) const override
Decide whether to accept a document.
Definition: api_db.cc:345
MyWeight()
Definition: api_db.cc:1711
std::string name() const override
Return the name of this weighting scheme, e.g.
Definition: api_db.cc:1713
void init(double factor) override
Allow the subclass to perform any initialisation it needs to.
Definition: api_db.cc:1708
MyWeight * unserialise(const string &) const override
Unserialise parameters.
Definition: api_db.cc:1715
MyWeight * clone() const override
Clone this object.
Definition: api_db.cc:1705
~MyWeight()
Definition: api_db.cc:1712
double scale_factor
Definition: api_db.cc:1702
string serialise() const override
Return this object's parameters serialised as a single string.
Definition: api_db.cc:1714
double get_sumpart(Xapian::termcount, Xapian::termcount, Xapian::termcount, Xapian::termcount) const override
Calculate the weight contribution for this object's term to a document.
Definition: api_db.cc:1718
double get_maxpart() const override
Return an upper bound on what get_sumpart() can return for any document.
Definition: api_db.cc:1724
DatabaseCreateError indicates a failure to create a database.
Definition: error.h:439
DatabaseLockError indicates failure to lock a database.
Definition: error.h:481
Indicates an attempt to access a database not present.
Definition: error.h:1043
DatabaseOpeningError indicates failure to open a database.
Definition: error.h:569
An indexed database of documents.
Definition: database.h:75
Xapian::doccount get_termfreq(std::string_view term) const
Get the number of documents indexed by a specified term.
Definition: database.cc:262
static size_t check(std::string_view path, int opts=0, std::ostream *out=NULL)
Check the integrity of a database or database table.
Definition: database.h:669
PostingIterator postlist_begin(std::string_view term) const
Start iterating the postings of a term.
Definition: database.cc:192
void keep_alive()
Send a keep-alive message.
Definition: database.cc:385
double get_avlength() const
Old name for get_average_length() for backward compatibility.
Definition: database.h:322
void add_database(const Database &other)
Add shards from another Database.
Definition: database.h:109
Xapian::termcount get_doclength(Xapian::docid did) const
Get the length of a specified document.
Definition: database.cc:341
bool term_exists(std::string_view term) const
Test is a particular term is present in any document.
Definition: database.cc:378
TermIterator allterms_end(std::string_view={}) const noexcept
End iterator corresponding to allterms_begin(prefix).
Definition: database.h:307
Xapian::termcount get_collection_freq(std::string_view term) const
Get the total number of occurrences of a specified term.
Definition: database.cc:273
Xapian::doccount get_doccount() const
Get the number of documents in the database.
Definition: database.cc:233
PostingIterator postlist_end(std::string_view) const noexcept
End iterator corresponding to postlist_begin().
Definition: database.h:258
Xapian::docid get_lastdocid() const
Get the highest document id which has been used in the database.
Definition: database.cc:239
TermIterator allterms_begin(std::string_view prefix={}) const
Start iterating all terms in the database with a given prefix.
Definition: database.cc:209
Xapian::Document get_document(Xapian::docid did, unsigned flags=0) const
Get a document from the database.
Definition: database.cc:368
Xapian::termcount get_unique_terms(Xapian::docid did) const
Get the number of unique terms in a specified document.
Definition: database.cc:350
Class representing a document.
Definition: document.h:64
std::string get_data() const
Get the document data.
Definition: document.cc:75
std::string get_value(Xapian::valueno slot) const
Read a value slot in this document.
Definition: document.cc:185
Iterator over a Xapian::ESet.
Definition: eset.h:157
double get_weight() const
Get the weight for the current position.
Class representing a list of search results.
Definition: eset.h:42
Xapian::termcount size() const
Return number of items in this ESet object.
ESetIterator end() const
Return iterator pointing to just after the last item in this ESet.
Definition: eset.h:329
ESetIterator begin() const
Return iterator pointing to the first item in this ESet.
Definition: eset.h:324
Querying session.
Definition: enquire.h:57
void set_weighting_scheme(const Weight &weight)
Set the weighting scheme to use.
Definition: enquire.cc:85
static const int USE_EXACT_TERMFREQ
Flag telling get_eset() to always use the exact term frequency.
Definition: enquire.h:479
MSet get_mset(doccount first, doccount maxitems, doccount checkatleast=0, const RSet *rset=NULL, const MatchDecider *mdecider=NULL) const
Run the query.
Definition: enquire.cc:200
TermIterator get_matching_terms_begin(docid did) const
Iterate query terms matching a document.
Definition: enquire.cc:210
void set_sort_by_value_then_relevance(valueno sort_key, bool reverse)
Set the sorting to be by value, then by relevance for documents with the same value.
Definition: enquire.cc:123
void set_cutoff(int percent_threshold, double weight_threshold=0)
Set lower bounds on percentage and/or weight.
Definition: enquire.cc:172
void set_query(const Query &query, termcount query_length=0)
Set the query.
Definition: enquire.cc:72
ESet get_eset(termcount maxitems, const RSet &rset, int flags=0, const ExpandDecider *edecider=NULL, double min_weight=0.0) const
Perform query expansion.
Definition: enquire.cc:231
void set_sort_by_relevance_then_value(valueno sort_key, bool reverse)
Set the sorting to be by relevance then value.
Definition: enquire.cc:144
void set_sort_by_value(valueno sort_key, bool reverse)
Set the sorting to be by value only.
Definition: enquire.cc:103
void set_collapse_key(valueno collapse_key, doccount collapse_max=1)
Control collapsing of results.
Definition: enquire.cc:165
void set_docid_order(docid_order order)
Set sort order for document IDs.
Definition: enquire.cc:91
@ DESCENDING
docids sort in descending order.
Definition: enquire.h:134
@ ASCENDING
docids sort in ascending order (default)
Definition: enquire.h:132
TermIterator get_matching_terms_end(docid) const noexcept
End iterator corresponding to get_matching_terms_begin().
Definition: enquire.h:435
const std::string & get_msg() const noexcept
Message giving details of the error, intended for human consumption.
Definition: error.h:111
const std::string & get_context() const noexcept
Optional context information.
Definition: error.h:121
Indicates an attempt to use a feature which is unavailable.
Definition: error.h:707
Iterator over a Xapian::MSet.
Definition: mset.h:535
Xapian::Document get_document() const
Get the Document object for the current position.
Definition: msetiterator.cc:45
Class representing a list of search results.
Definition: mset.h:46
Xapian::doccount size() const
Return number of items in this MSet object.
Definition: mset.cc:374
Xapian::doccount get_uncollapsed_matches_upper_bound() const
Upper bound on the total number of matching documents before collapsing.
Definition: mset.cc:356
Xapian::doccount get_uncollapsed_matches_estimated() const
Estimate of the total number of matching documents before collapsing.
Definition: mset.cc:346
Xapian::doccount get_uncollapsed_matches_lower_bound() const
Lower bound on the total number of matching documents before collapsing.
Definition: mset.cc:340
bool empty() const
Return true if this MSet object is empty.
Definition: mset.h:467
Xapian::doccount get_matches_upper_bound() const
Upper bound on the total number of matching documents.
Definition: mset.cc:334
MSetIterator begin() const
Return iterator pointing to the first item in this MSet.
Definition: mset.h:786
double get_max_attained() const
The maximum weight attained by any document.
Definition: mset.cc:362
Xapian::doccount get_matches_lower_bound() const
Lower bound on the total number of matching documents.
Definition: mset.cc:318
MSetIterator end() const
Return iterator pointing to just after the last item in this MSet.
Definition: mset.h:791
Xapian::doccount get_matches_estimated() const
Estimate of the total number of matching documents.
Definition: mset.cc:324
Abstract base class for match deciders.
Definition: matchdecider.h:37
Indicates a problem communicating with a remote database.
Definition: error.h:791
Indicates a timeout expired while communicating with a remote database.
Definition: error.h:833
Class for iterating over a list of terms.
Xapian::termcount get_wdf() const
Return the wdf for the document at the current position.
void skip_to(Xapian::docid did)
Advance the iterator to document did.
std::string get_description() const
Return a string describing this object.
Xapian::termcount get_doclength() const
Return the length of the document at the current position.
Xapian::termcount get_unique_terms() const
Return the number of unique terms in the current document.
Class representing a query.
Definition: query.h:45
@ OP_OR
Match documents which at least one subquery matches.
Definition: query.h:92
static const Xapian::Query MatchAll
A query matching all documents.
Definition: query.h:75
Class representing a set of documents judged as relevant.
Definition: rset.h:39
void add_document(Xapian::docid did)
Mark a document as relevant.
Definition: rset.cc:55
Class representing a stemming algorithm.
Definition: stem.h:74
Parses a piece of text and generate terms.
Definition: termgenerator.h:49
void index_text(const Xapian::Utf8Iterator &itor, Xapian::termcount wdf_inc=1, std::string_view prefix={})
Index some text.
void set_document(const Xapian::Document &doc)
Set the current document.
void set_stemming_strategy(stem_strategy strategy)
Set the stemming strategy.
void set_stemmer(const Xapian::Stem &stemmer)
Set the Xapian::Stem object to be used for generating stemmed terms.
Class for iterating over a list of terms.
Definition: termiterator.h:41
void skip_to(std::string_view term)
Advance the iterator to term term.
Xapian::doccount get_termfreq() const
Return the term frequency for the term at the current position.
UnimplementedError indicates an attempt to use an unimplemented feature.
Definition: error.h:313
MatchDecider filtering results based on whether document values are in a user-defined set.
void add_value(const std::string &value)
Add a value to the test set.
Abstract base class for weighting schemes.
Definition: weight.h:38
This class provides read/write access to a database.
Definition: database.h:964
void replace_document(Xapian::docid did, const Xapian::Document &document)
Replace a document in the database.
Definition: database.cc:582
void commit()
Commit pending modifications.
Definition: database.cc:543
Xapian::docid add_document(const Xapian::Document &doc)
Add a document to the database.
Definition: database.cc:561
PositionList * p
void sleep(double t)
Sleep until the time represented by this object.
Definition: realtime.h:127
const valueno BAD_VALUENO
Reserved value to indicate "no valueno".
Definition: types.h:100
const int DB_CREATE
Create a new database.
Definition: constants.h:43
const int DB_CREATE_OR_OPEN
Create database if it doesn't already exist.
Definition: constants.h:34
unsigned XAPIAN_TERMCOUNT_BASE_TYPE termcount
A counts of terms.
Definition: types.h:64
const int DB_BACKEND_STUB
Open a stub database file.
Definition: constants.h:166
const int DB_OPEN
Open an existing database.
Definition: constants.h:49
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
const int DB_BACKEND_HONEY
Use the honey backend.
Definition: constants.h:197
const int DB_BACKEND_GLASS
Use the glass backend.
Definition: constants.h:157
unsigned XAPIAN_DOCID_BASE_TYPE docid
A unique identifier for a document.
Definition: types.h:51
const int DB_CREATE_OR_OVERWRITE
Create database if it doesn't already exist, or overwrite if it does.
Definition: constants.h:37
include <netdb.h>, with portability workarounds.
include <sys/stat.h> with portability enhancements
<unistd.h>, but with compat.
static Xapian::Stem stemmer
Definition: stemtest.cc:42
#define TEST_REL(A, REL, B)
Test a relation holds,e.g. TEST_REL(a,>,b);.
Definition: testmacros.h:35
std::ostringstream tout
The debug printing stream.
Definition: testsuite.cc:104
a generic test suite engine
#define FAIL_TEST(MSG)
Fail the current testcase with message MSG.
Definition: testsuite.h:65
#define SKIP_TEST(MSG)
Skip the current testcase with message MSG.
Definition: testsuite.h:71
#define TEST_EQUAL(a, b)
Test for equality of two things.
Definition: testsuite.h:276
#define TEST_STRINGS_EQUAL(a, b)
Test for equality of two strings.
Definition: testsuite.h:285
#define TEST_EQUAL_DOUBLE(a, b)
Test two doubles for near equality.
Definition: testsuite.h:293
#define TEST(a)
Test a condition, without an additional explanation for failure.
Definition: testsuite.h:273
#define TEST_NOT_EQUAL(a, b)
Test for non-equality of two things.
Definition: testsuite.h:303
#define TEST_AND_EXPLAIN(a, b)
Test a condition, and display the test with an extra explanation if the condition fails.
Definition: testsuite.h:265
bool mset_range_is_same_weights(const Xapian::MSet &mset1, unsigned int first1, const Xapian::MSet &mset2, unsigned int first2, unsigned int count)
Definition: testutils.cc:110
void mset_expect_order(const Xapian::MSet &A, Xapian::docid d1, Xapian::docid d2, Xapian::docid d3, Xapian::docid d4, Xapian::docid d5, Xapian::docid d6, Xapian::docid d7, Xapian::docid d8, Xapian::docid d9, Xapian::docid d10, Xapian::docid d11, Xapian::docid d12)
Definition: testutils.cc:224
bool mset_range_is_same(const Xapian::MSet &mset1, unsigned int first1, const Xapian::MSet &mset2, unsigned int first2, unsigned int count)
Definition: testutils.cc:45
Xapian-specific test helper functions and macros.
#define TEST_EXCEPTION(TYPE, CODE)
Check that CODE throws exactly Xapian exception TYPE.
Definition: testutils.h:112
#define TEST_EXCEPTION_BASE_CLASS(TYPE, CODE)
Check that CODE throws Xapian exception derived from TYPE.
Definition: testutils.h:108
#define TEST_MSET_SIZE(M, S)
Check MSet M has size S.
Definition: testutils.h:77
void touch(const string &filename)
Touch a file, just like the Unix "touch" command.
Definition: unixcmds.cc:166
void rm_rf(const string &filename)
Remove a directory and contents, just like the Unix "rm -rf" command.
Definition: unixcmds.cc:111
C++ function versions of useful Unix commands.
static int verbose
Definition: xapian-delve.cc:46
Public interfaces for the Xapian library.