xapian-core  1.4.25
api_backend.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2018,2019 Olly Betts
5  * Copyright (C) 2010 Richard Boulton
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20  * USA
21  */
22 
23 #include <config.h>
24 
25 #include "api_backend.h"
26 
27 #define XAPIAN_DEPRECATED(X) X
28 #include <xapian.h>
29 
30 #include "backendmanager.h"
31 #include "errno_to_string.h"
32 #include "filetests.h"
33 #include "str.h"
34 #include "testrunner.h"
35 #include "testsuite.h"
36 #include "testutils.h"
37 #include "unixcmds.h"
38 
39 #include "apitest.h"
40 
41 #include "safefcntl.h"
42 #include "safesysstat.h"
43 #include "safeunistd.h"
44 #ifdef HAVE_SOCKETPAIR
45 # include "safesyssocket.h"
46 # include <signal.h>
47 # include "safesyswait.h"
48 #endif
49 
50 #include <cerrno>
51 #include <fstream>
52 #include <iterator>
53 
54 using namespace std;
55 
57 DEFINE_TESTCASE(lockfileumask1, chert || glass) {
58 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
59  mode_t old_umask = umask(022);
60  try {
62 
63  string path = get_named_writable_database_path("lockfileumask1");
64  path += "/flintlock";
65 
66  struct stat statbuf;
67  TEST(stat(path.c_str(), &statbuf) == 0);
68  TEST_EQUAL(statbuf.st_mode & 0777, 0644);
69  } catch (...) {
70  umask(old_umask);
71  throw;
72  }
73 
74  umask(old_umask);
75 #endif
76 }
77 
79 DEFINE_TESTCASE(totaldoclen1, writable) {
81  Xapian::Document doc;
82  doc.add_posting("foo", 1, 2000000000);
83  db.add_document(doc);
84  Xapian::Document doc2;
85  doc2.add_posting("bar", 1, 2000000000);
86  db.add_document(doc2);
87  TEST_EQUAL(db.get_avlength(), 2000000000);
88  TEST_EQUAL(db.get_total_length(), 4000000000ull);
89  db.commit();
90  TEST_EQUAL(db.get_avlength(), 2000000000);
91  TEST_EQUAL(db.get_total_length(), 4000000000ull);
92  for (int i = 0; i != 20; ++i) {
93  Xapian::Document doc3;
94  doc3.add_posting("count" + str(i), 1, 2000000000);
95  db.add_document(doc3);
96  }
97  TEST_EQUAL(db.get_avlength(), 2000000000);
98  TEST_EQUAL(db.get_total_length(), 44000000000ull);
99  db.commit();
100  TEST_EQUAL(db.get_avlength(), 2000000000);
101  TEST_EQUAL(db.get_total_length(), 44000000000ull);
102  if (get_dbtype() != "inmemory") {
103  // InMemory doesn't support get_writable_database_as_database().
105  TEST_EQUAL(dbr.get_avlength(), 2000000000);
106  TEST_EQUAL(dbr.get_total_length(), 44000000000ull);
107  }
108 }
109 
110 // Check that exceeding 32bit in combined database doesn't cause a problem
111 // when using 64bit docids.
112 DEFINE_TESTCASE(exceed32bitcombineddb1, writable) {
113  // Test case is for 64-bit Xapian::docid.
114  // FIXME: Though we should check that the overflow is handled gracefully
115  // for 32-bit...
116  if (sizeof(Xapian::docid) == 4) return;
117 
118  // The InMemory backend uses a vector for the documents, so trying to add
119  // a document with the maximum docid is likely to fail because we can't
120  // allocate enough memory!
121  SKIP_TEST_FOR_BACKEND("inmemory");
122 
124  Xapian::Document doc;
125  doc.set_data("prose");
126  doc.add_term("word");
127  Xapian::docid max_32bit_id = 0xffffffff;
128  db1.replace_document(max_32bit_id, doc);
129  db1.commit();
130 
132 
133  Xapian::Database db;
134  db.add_database(db1);
135  db.add_database(db2);
136 
137  Xapian::Enquire enquire(db);
139  Xapian::MSet mymset = enquire.get_mset(0, 10);
140 
141  TEST_EQUAL(2, mymset.size());
142 
143  // We can't usefully check the shard docid if the testharness backend is
144  // multi.
145  bool multi = (db1.size() > 1);
146  for (Xapian::MSetIterator i = mymset.begin(); i != mymset.end(); ++i) {
147  doc = i.get_document();
148  if (!multi)
149  TEST_EQUAL(doc.get_docid(), max_32bit_id);
150  TEST_EQUAL(doc.get_data(), "prose");
151  }
152 }
153 
154 DEFINE_TESTCASE(dbstats1, backend) {
155  Xapian::Database db = get_database("etext");
156 
157  // Use precalculated values to avoid expending CPU cycles to calculate
158  // these every time without improving test coverage.
159  const Xapian::termcount min_len = 2;
160  const Xapian::termcount max_len = 532;
161  const Xapian::termcount max_wdf = 22;
162 
163  if (get_dbtype() != "inmemory") {
164  // Should be exact as no deletions have happened.
165  TEST_EQUAL(db.get_doclength_upper_bound(), max_len);
166  TEST_EQUAL(db.get_doclength_lower_bound(), min_len);
167  } else {
168  // For inmemory, we usually give rather loose bounds.
169  TEST_REL(db.get_doclength_upper_bound(),>=,max_len);
170  TEST_REL(db.get_doclength_lower_bound(),<=,min_len);
171  }
172 
173  if (get_dbtype() != "inmemory" &&
174  get_dbtype().find("remote") == string::npos) {
175  TEST_EQUAL(db.get_wdf_upper_bound("the"), max_wdf);
176  } else {
177  // For inmemory and remote backends, we usually give rather loose
178  // bounds (remote matches use tighter bounds, but querying the
179  // wdf bound gives a looser one).
180  TEST_REL(db.get_wdf_upper_bound("the"),>=,max_wdf);
181  }
182 
183  // This failed with an assertion during development between 1.3.1 and
184  // 1.3.2.
185  TEST_EQUAL(db.get_wdf_upper_bound(""), 0);
186 }
187 
188 // Check stats with a single document. In a multi-database situation, this
189 // gave 0 for get-_doclength_lower_bound() in 1.3.2.
190 DEFINE_TESTCASE(dbstats2, backend) {
191  Xapian::Database db = get_database("apitest_onedoc");
192 
193  // Use precalculated values to avoid expending CPU cycles to calculate
194  // these every time without improving test coverage.
195  const Xapian::termcount min_len = 15;
196  const Xapian::termcount max_len = 15;
197  const Xapian::termcount max_wdf = 7;
198 
199  if (get_dbtype() != "inmemory") {
200  // Should be exact as no deletions have happened.
201  TEST_EQUAL(db.get_doclength_upper_bound(), max_len);
202  TEST_EQUAL(db.get_doclength_lower_bound(), min_len);
203  } else {
204  // For inmemory, we usually give rather loose bounds.
205  TEST_REL(db.get_doclength_upper_bound(),>=,max_len);
206  TEST_REL(db.get_doclength_lower_bound(),<=,min_len);
207  }
208 
209  if (get_dbtype() != "inmemory" &&
210  get_dbtype().find("remote") == string::npos) {
211  TEST_EQUAL(db.get_wdf_upper_bound("word"), max_wdf);
212  } else {
213  // For inmemory and remote backends, we usually give rather loose
214  // bounds (remote matches use tighter bounds, but querying the
215  // wdf bound gives a looser one).
216  TEST_REL(db.get_wdf_upper_bound("word"),>=,max_wdf);
217  }
218 
219  TEST_EQUAL(db.get_wdf_upper_bound(""), 0);
220 }
221 
223 DEFINE_TESTCASE(alldocspl3, backend) {
224  Xapian::Database db = get_database(string());
225 
226  TEST_EQUAL(db.get_termfreq(string()), 0);
227  TEST_EQUAL(db.get_collection_freq(string()), 0);
228  TEST(db.postlist_begin(string()) == db.postlist_end(string()));
229 }
230 
232 DEFINE_TESTCASE(modifiedpostlist1, writable) {
234  Xapian::Document a, b;
235  Xapian::Enquire enq(db);
236 
237  a.add_term("T");
238  enq.set_query(Xapian::Query("T"));
239 
240  db.replace_document(2, a);
241  db.commit();
242  db.replace_document(1, a);
243  db.replace_document(1, b);
244 
245  mset_expect_order(enq.get_mset(0, 2), 2);
246 }
247 
249 DEFINE_TESTCASE(doclenaftercommit1, writable) {
254  db.commit();
255  TEST_EQUAL(db.get_doclength(1), 0);
256  TEST_EQUAL(db.get_unique_terms(1), 0);
257 }
258 
259 DEFINE_TESTCASE(valuesaftercommit1, writable) {
261  Xapian::Document doc;
262  doc.add_value(0, "value");
263  db.replace_document(2, doc);
264  db.commit();
265  db.replace_document(1, doc);
266  db.replace_document(3, doc);
267  TEST_EQUAL(db.get_document(3).get_value(0), "value");
268  db.commit();
269  TEST_EQUAL(db.get_document(3).get_value(0), "value");
270 }
271 
272 DEFINE_TESTCASE(lockfilefd0or1, chert || glass) {
273 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
274  int old_stdin = dup(0);
275  int old_stdout = dup(1);
276  try {
277  // With fd 0 available.
278  close(0);
279  {
283  }
284  // With fd 0 and fd 1 available.
285  close(1);
286  {
290  }
291  // With fd 1 available.
292  dup2(old_stdin, 0);
293  {
297  }
298  } catch (...) {
299  dup2(old_stdin, 0);
300  dup2(old_stdout, 1);
301  close(old_stdin);
302  close(old_stdout);
303  throw;
304  }
305 
306  dup2(old_stdout, 1);
307  close(old_stdin);
308  close(old_stdout);
309 #endif
310 }
311 
313 DEFINE_TESTCASE(lockfilealreadyopen1, chert || glass) {
314  // Ensure database has been created.
315  (void)get_named_writable_database("lockfilealreadyopen1");
316  string path = get_named_writable_database_path("lockfilealreadyopen1");
317  int fd = ::open((path + "/flintlock").c_str(), O_RDONLY);
318  TEST(fd != -1);
319  try {
323  );
324  } catch (...) {
325  close(fd);
326  throw;
327  }
328  close(fd);
329 }
330 
332 DEFINE_TESTCASE(testlock1, chert || glass) {
333  Xapian::Database rdb;
334  TEST(!rdb.locked());
335  {
337  TEST(db.locked());
338  Xapian::Database db_as_database = db;
339  TEST(db_as_database.locked());
340  TEST(!rdb.locked());
342  TEST(db.locked());
343  TEST(db_as_database.locked());
344  try {
345  TEST(rdb.locked());
346  } catch (const Xapian::FeatureUnavailableError&) {
347  SKIP_TEST("Database::locked() not supported on this platform");
348  }
349  db_as_database = rdb;
350  TEST(db.locked());
351  TEST(db_as_database.locked());
352  TEST(rdb.locked());
353  db_as_database.close();
354  TEST(db.locked());
355  TEST(rdb.locked());
356  // After close(), locked() should either work as if close() hadn't been
357  // called or throw Xapian::DatabaseClosedError.
358  try {
359  TEST(db_as_database.locked());
360  } catch (const Xapian::DatabaseClosedError&) {
361  }
362  db.close();
363  TEST(!rdb.locked());
364  try {
365  TEST(!db_as_database.locked());
366  } catch (const Xapian::DatabaseClosedError&) {
367  }
368  }
369  TEST(!rdb.locked());
370 }
371 
376 DEFINE_TESTCASE(testlock2, backend && !writable) {
377  Xapian::Database db = get_database("apitest_simpledata");
378  TEST(!db.locked());
379  db.close();
380  TEST(!db.locked());
381 }
382 
395 DEFINE_TESTCASE(testlock3, inmemory) {
396  Xapian::Database db = get_database("apitest_simpledata");
397  TEST(db.locked());
398  db.close();
399  TEST(!db.locked());
400 }
401 
403 DEFINE_TESTCASE(testlock4, chert || glass) {
404  Xapian::Database db = get_writable_database("apitest_simpledata");
405  // Even if we don't have a way to test the lock on the current platform,
406  // this should know the database is locked because this object holds the
407  // lock.
408  TEST(db.locked());
409  db.close();
410  try {
411  TEST(!db.locked());
412  } catch (const Xapian::FeatureUnavailableError&) {
413  SKIP_TEST("Database::locked() not supported on this platform");
414  }
415 }
416 
418  mutable bool called;
419 
420  public:
421  CheckMatchDecider() : called(false) { }
422 
423  bool operator()(const Xapian::Document &) const {
424  called = true;
425  return true;
426  }
427 
428  bool was_called() const { return called; }
429 };
430 
432 DEFINE_TESTCASE(matchdecider4, remote) {
433  Xapian::Database db(get_database("apitest_simpledata"));
434  Xapian::Enquire enquire(db);
435  enquire.set_query(Xapian::Query("paragraph"));
436 
437  CheckMatchDecider mdecider;
438  Xapian::MSet mset;
439 
441  mset = enquire.get_mset(0, 10, NULL, &mdecider));
442  TEST(!mdecider.was_called());
443 }
444 
448 DEFINE_TESTCASE(replacedoc7, writable && !inmemory && !remote) {
449  // The inmemory backend doesn't batch changes, so there's nothing to
450  // check there.
451  //
452  // The remote backend doesn't implement the lazy replacement of documents
453  // optimisation currently.
455  Xapian::Document doc;
456  doc.set_data("fish");
457  doc.add_term("Hlocalhost");
458  doc.add_posting("hello", 1);
459  doc.add_posting("world", 2);
460  doc.add_value(1, "myvalue");
461  db.add_document(doc);
462  db.commit();
463 
464  // We add a second document, and then replace the first document with
465  // itself 10000 times. If the document count for the database reopened
466  // read-only is 2, then we triggered an automatic commit.
467 
468  doc.add_term("XREV2");
469  db.add_document(doc);
470 
471  for (int i = 0; i < 10000; ++i) {
472  doc = db.get_document(1);
473  db.replace_document(1, doc);
474  }
475 
477  TEST_EQUAL(rodb.get_doccount(), 1);
478 
479  db.flush();
480  TEST(rodb.reopen());
481 
482  TEST_EQUAL(rodb.get_doccount(), 2);
483 }
484 
489 DEFINE_TESTCASE(replacedoc8, writable) {
491  {
492  Xapian::Document doc;
493  doc.set_data("fish");
494  doc.add_term("takeaway");
495  db.add_document(doc);
496  }
497  db.delete_document(1);
498  {
499  Xapian::Document doc;
500  doc.set_data("chips");
501  doc.add_term("takeaway", 2);
502  db.replace_document(1, doc);
503  }
504  db.flush();
505  TEST_EQUAL(db.get_collection_freq("takeaway"), 2);
506  Xapian::PostingIterator p = db.postlist_begin("takeaway");
507  TEST(p != db.postlist_end("takeaway"));
508  TEST_EQUAL(p.get_wdf(), 2);
509 }
510 
515 DEFINE_TESTCASE(replacedoc9, writable) {
517  {
518  Xapian::Document doc;
519  doc.set_data("food");
520  doc.add_posting("falafel", 1);
521  db.add_document(doc);
522  }
523  db.commit();
524  Xapian::Document doc = db.get_document(1);
525  doc.clear_terms();
526  doc.add_term("falafel");
527  db.replace_document(1, doc);
528  db.commit();
529 
530  // The positions should have been removed, but the bug meant they weren't.
531  TEST_EQUAL(db.positionlist_begin(1, "falafel"),
532  db.positionlist_end(1, "falafel"));
533 }
534 
536 DEFINE_TESTCASE(databasemodified1, writable && !inmemory && !multi) {
537  // The inmemory backend doesn't support revisions.
538  //
539  // With multi, DatabaseModifiedError doesn't trigger as easily.
541  Xapian::Document doc;
542  doc.set_data("cargo");
543  doc.add_term("abc");
544  doc.add_term("def");
545  doc.add_term("ghi");
546  const int N = 500;
547  for (int i = 0; i < N; ++i) {
548  db.add_document(doc);
549  }
550  db.commit();
551 
553  db.add_document(doc);
554  db.commit();
555 
556  db.add_document(doc);
557  db.commit();
558 
559  db.add_document(doc);
560  try {
561  TEST_EQUAL(*rodb.termlist_begin(N - 1), "abc");
562  FAIL_TEST("Expected DatabaseModifiedError wasn't thrown");
563  } catch (const Xapian::DatabaseModifiedError &) {
564  }
565 
566  try {
567  Xapian::Enquire enq(rodb);
568  enq.set_query(Xapian::Query("abc"));
569  Xapian::MSet mset = enq.get_mset(0, 10);
570  FAIL_TEST("Expected DatabaseModifiedError wasn't thrown");
571  } catch (const Xapian::DatabaseModifiedError &) {
572  }
573 }
574 
576 DEFINE_TESTCASE(qpmemoryleak1, writable && !inmemory) {
577  // Inmemory never throws DatabaseModifiedError.
579  Xapian::Document doc;
580 
581  doc.add_term("foo");
582  for (int i = 100; i < 120; ++i) {
583  doc.add_term(str(i));
584  }
585 
586  for (int j = 0; j < 50; ++j) {
587  wdb.add_document(doc);
588  }
589  wdb.commit();
590 
592  Xapian::QueryParser queryparser;
593  queryparser.set_database(database);
595  for (int k = 0; k < 1000; ++k) {
596  wdb.add_document(doc);
597  wdb.commit();
598  (void)queryparser.parse_query("1", queryparser.FLAG_PARTIAL);
599  }
600  SKIP_TEST("didn't manage to trigger DatabaseModifiedError");
601  );
602 }
603 
604 static void
606 {
607  const char * value0 =
608  "ABBCDEFGHIJKLMMNOPQQRSTTUUVVWXYZZaabcdefghhijjkllmnopqrsttuvwxyz";
609  const char * value1 =
610  "EMLEMMMMMMMNMMLMELEDNLEDMLMLDMLMLMLMEDGFHPOPBAHJIQJNGRKCGF";
611  while (*value0) {
612  Xapian::Document doc;
613  doc.add_value(0, string(1, *value0++));
614  if (*value1) {
615  doc.add_value(1, string(1, *value1++));
616  doc.add_term("K1");
617  }
618  db.add_document(doc);
619  }
620 }
621 
623 DEFINE_TESTCASE(msize1, backend) {
625  Xapian::Enquire enq(db);
626  enq.set_sort_by_value(1, false);
627  enq.set_collapse_key(0);
628  enq.set_query(Xapian::Query("K1"));
629 
630  Xapian::MSet mset = enq.get_mset(0, 60);
634  TEST_EQUAL(lb, ub);
635  TEST_EQUAL(lb, est);
636 
637  Xapian::MSet mset2 = enq.get_mset(50, 10, 1000);
641  TEST_EQUAL(lb2, ub2);
642  TEST_EQUAL(lb2, est2);
643  TEST_EQUAL(est, est2);
644 
645  Xapian::MSet mset3 = enq.get_mset(0, 10, 1000);
649  TEST_EQUAL(lb3, ub3);
650  TEST_EQUAL(lb3, est3);
651  TEST_EQUAL(est, est3);
652 }
653 
654 static void
656 {
657  const char * value0 = "AAABCDEEFGHIIJJKLLMNNOOPPQQRSTTUVWXYZ";
658  const char * value1 = "MLEMNMLMLMEDEDEMLEMLMLMLPOAHGF";
659  while (*value0) {
660  Xapian::Document doc;
661  doc.add_value(0, string(1, *value0++));
662  if (*value1) {
663  doc.add_value(1, string(1, *value1++));
664  doc.add_term("K1");
665  }
666  db.add_document(doc);
667  }
668 }
669 
671 DEFINE_TESTCASE(msize2, backend) {
673  Xapian::Enquire enq(db);
674  enq.set_sort_by_value(1, false);
675  enq.set_collapse_key(0);
676  enq.set_query(Xapian::Query("K1"));
677 
678  Xapian::MSet mset = enq.get_mset(0, 60);
682  TEST_EQUAL(lb, ub);
683  TEST_EQUAL(lb, est);
684 
685  Xapian::MSet mset2 = enq.get_mset(50, 10, 1000);
689  TEST_EQUAL(lb2, ub2);
690  TEST_EQUAL(lb2, est2);
691  TEST_EQUAL(est, est2);
692 
693  Xapian::MSet mset3 = enq.get_mset(0, 10, 1000);
697  TEST_EQUAL(lb3, ub3);
698  TEST_EQUAL(lb3, est3);
699  TEST_EQUAL(est, est3);
700 }
701 
702 static void
704 {
705  for (int n = 1; n != 50; ++n) {
706  Xapian::Document doc;
707  for (int i = 1; i != 50; ++i) {
708  if (n % i == 0)
709  doc.add_term("N" + str(i));
710  }
711  db.add_document(doc);
712  }
713 }
714 
716 DEFINE_TESTCASE(xordecay1, backend) {
718  Xapian::Enquire enq(db);
720  Xapian::Query("N10"),
722  Xapian::Query("N2"),
723  Xapian::Query("N3"))));
724  Xapian::MSet mset1 = enq.get_mset(0, 1);
725  Xapian::MSet msetall = enq.get_mset(0, db.get_doccount());
726 
727  TEST(mset_range_is_same(mset1, 0, msetall, 0, mset1.size()));
728 }
729 
730 static void
732 {
733  const char * p = "VJ=QC]LUNTaARLI;715RR^];A4O=P4ZG<2CS4EM^^VS[A6QENR";
734  for (int d = 0; p[d]; ++d) {
735  int l = int(p[d] - '0');
736  Xapian::Document doc;
737  for (int n = 1; n < l; ++n) {
738  doc.add_term("N" + str(n));
739  if (n % (d + 1) == 0) {
740  doc.add_term("M" + str(n));
741  }
742  }
743  db.add_document(doc);
744  }
745 }
746 
748 DEFINE_TESTCASE(ordecay1, backend) {
750  Xapian::Enquire enq(db);
752  Xapian::Query("N20"),
753  Xapian::Query("N21")));
754 
755  Xapian::MSet msetall = enq.get_mset(0, db.get_doccount());
756  for (unsigned int i = 1; i < msetall.size(); ++i) {
757  Xapian::MSet submset = enq.get_mset(0, i);
758  TEST(mset_range_is_same(submset, 0, msetall, 0, submset.size()));
759  }
760 }
761 
765 DEFINE_TESTCASE(ordecay2, backend) {
767  Xapian::Enquire enq(db);
768  std::vector<Xapian::Query> q;
769  q.push_back(Xapian::Query("M20"));
770  q.push_back(Xapian::Query("N21"));
771  q.push_back(Xapian::Query("N22"));
773  Xapian::Query("N25"),
775  q.begin(),
776  q.end())));
777 
778  Xapian::MSet msetall = enq.get_mset(0, db.get_doccount());
779  for (unsigned int i = 1; i < msetall.size(); ++i) {
780  Xapian::MSet submset = enq.get_mset(0, i);
781  TEST(mset_range_is_same(submset, 0, msetall, 0, submset.size()));
782  }
783 }
784 
785 static void
787 {
788  static const unsigned t1[] = {2, 4, 6, 8, 10};
789  static const unsigned t2[] = {6, 7, 8, 11, 12, 13, 14, 15, 16, 17};
790  static const unsigned t3[] = {3, 7, 8, 11, 12, 13, 14, 15, 16, 17};
791 
792  for (unsigned i = 1; i <= 17; ++i) {
793  Xapian::Document doc;
794  db.replace_document(i, doc);
795  }
796  for (unsigned i : t1) {
797  Xapian::Document doc(db.get_document(i));
798  doc.add_term("T1");
799  db.replace_document(i, doc);
800  }
801  for (unsigned i : t2) {
802  Xapian::Document doc(db.get_document(i));
803  doc.add_term("T2");
804  if (i < 17) {
805  doc.add_term("T2_lowfreq");
806  }
807  doc.add_value(2, "1");
808  db.replace_document(i, doc);
809  }
810  for (unsigned i : t3) {
811  Xapian::Document doc(db.get_document(i));
812  doc.add_term("T3");
813  if (i < 17) {
814  doc.add_term("T3_lowfreq");
815  }
816  doc.add_value(3, "1");
817  db.replace_document(i, doc);
818  }
819 }
820 
824 DEFINE_TESTCASE(orcheck1, backend) {
826  Xapian::Enquire enq(db);
827  Xapian::Query q1("T1");
828  Xapian::Query q2("T2");
829  Xapian::Query q2l("T2_lowfreq");
830  Xapian::Query q3("T3");
831  Xapian::Query q3l("T3_lowfreq");
834 
835  tout << "Checking q2 OR q3\n";
838  mset_expect_order(enq.get_mset(0, db.get_doccount()), 8, 6);
839 
840  tout << "Checking q2l OR q3\n";
843  mset_expect_order(enq.get_mset(0, db.get_doccount()), 8, 6);
844 
845  tout << "Checking q2 OR q3l\n";
848  mset_expect_order(enq.get_mset(0, db.get_doccount()), 8, 6);
849 
850  tout << "Checking v2 OR q3\n";
853  mset_expect_order(enq.get_mset(0, db.get_doccount()), 8, 6);
854 
855  tout << "Checking q2 OR v3\n";
858  // Order of results in this one is different, because v3 gives no weight,
859  // both documents are in q2, and document 8 has a higher length.
860  mset_expect_order(enq.get_mset(0, db.get_doccount()), 6, 8);
861 
862 }
863 
868 DEFINE_TESTCASE(failedreplace1, chert || glass) {
870  Xapian::Document doc;
871  doc.add_term("foo");
872  db.add_document(doc);
873  Xapian::docid did = db.add_document(doc);
874  doc.add_term("abc");
875  doc.add_term(string(1000, 'm'));
876  doc.add_term("xyz");
878  db.commit();
879  TEST_EQUAL(db.get_doccount(), 0);
880  TEST_EQUAL(db.get_termfreq("foo"), 0);
881 }
882 
883 DEFINE_TESTCASE(failedreplace2, chert || glass) {
884  Xapian::WritableDatabase db(get_writable_database("apitest_simpledata"));
885  db.commit();
886  Xapian::doccount db_size = db.get_doccount();
887  Xapian::Document doc;
888  doc.set_data("wibble");
889  doc.add_term("foo");
890  doc.add_value(0, "seven");
891  db.add_document(doc);
892  Xapian::docid did = db.add_document(doc);
893  doc.add_term("abc");
894  doc.add_term(string(1000, 'm'));
895  doc.add_term("xyz");
896  doc.add_value(0, "six");
898  db.commit();
899  TEST_EQUAL(db.get_doccount(), db_size);
900  TEST_EQUAL(db.get_termfreq("foo"), 0);
901 }
902 
904 DEFINE_TESTCASE(phrase3, positional) {
905  Xapian::Database db = get_database("apitest_phrase");
906 
907  static const char * const phrase_words[] = { "phrase", "near" };
908  Xapian::Query q(Xapian::Query::OP_NEAR, phrase_words, phrase_words + 2, 12);
910 
911  Xapian::Enquire enquire(db);
912  enquire.set_query(q);
913  Xapian::MSet mset = enquire.get_mset(0, 5);
914 
915 }
916 
918 // Regression test for fix in 1.2.4.
919 DEFINE_TESTCASE(msetfirst2, backend) {
920  Xapian::Database db(get_database("apitest_simpledata"));
921  Xapian::Enquire enquire(db);
922  enquire.set_query(Xapian::Query("paragraph"));
923  Xapian::MSet mset;
924  // Before the fix, this tried to allocate too much memory.
925  mset = enquire.get_mset(0xfffffff0, 1);
926  TEST_EQUAL(mset.get_firstitem(), 0xfffffff0);
927  // Check that the number of documents gets clamped too.
928  mset = enquire.get_mset(1, 0xfffffff0);
929  TEST_EQUAL(mset.get_firstitem(), 1);
930  // Another regression test - MatchNothing used to give an MSet with
931  // get_firstitem() returning 0.
933  mset = enquire.get_mset(1, 1);
934  TEST_EQUAL(mset.get_firstitem(), 1);
935 }
936 
937 DEFINE_TESTCASE(bm25weight2, backend) {
938  Xapian::Database db(get_database("etext"));
939  Xapian::Enquire enquire(db);
940  enquire.set_query(Xapian::Query("the"));
941  enquire.set_weighting_scheme(Xapian::BM25Weight(0, 0, 0, 0, 1));
942  Xapian::MSet mset = enquire.get_mset(0, 100);
943  TEST_REL(mset.size(),>=,2);
944  double weight0 = mset[0].get_weight();
945  for (Xapian::doccount i = 1; i != mset.size(); ++i) {
946  TEST_EQUAL(weight0, mset[i].get_weight());
947  }
948 }
949 
950 DEFINE_TESTCASE(unigramlmweight2, backend) {
951  Xapian::Database db(get_database("etext"));
952  Xapian::Enquire enquire(db);
953  enquire.set_query(Xapian::Query("the"));
955  Xapian::MSet mset = enquire.get_mset(0, 100);
956  TEST_REL(mset.size(),>=,2);
957 }
958 
959 DEFINE_TESTCASE(tradweight2, backend) {
960  Xapian::Database db(get_database("etext"));
961  Xapian::Enquire enquire(db);
962  enquire.set_query(Xapian::Query("the"));
964  Xapian::MSet mset = enquire.get_mset(0, 100);
965  TEST_REL(mset.size(),>=,2);
966  double weight0 = mset[0].get_weight();
967  for (Xapian::doccount i = 1; i != mset.size(); ++i) {
968  TEST_EQUAL(weight0, mset[i].get_weight());
969  }
970 }
971 
972 // Regression test for bug fix in 1.2.9.
973 DEFINE_TESTCASE(emptydb1, backend) {
974  Xapian::Database db(get_database(string()));
975  static const Xapian::Query::op ops[] = {
987  };
988  for (Xapian::Query::op op : ops) {
989  tout << op << '\n';
990  Xapian::Enquire enquire(db);
992  enquire.set_query(query);
993  Xapian::MSet mset = enquire.get_mset(0, 10);
997  }
998 }
999 
1006 DEFINE_TESTCASE(multiargop1, backend) {
1007  Xapian::Database db(get_database("apitest_simpledata"));
1008  static const struct { unsigned hits; Xapian::Query::op op; } tests[] = {
1009  { 0, Xapian::Query::OP_AND },
1010  { 6, Xapian::Query::OP_OR },
1012  { 5, Xapian::Query::OP_XOR },
1014  { 0, Xapian::Query::OP_FILTER },
1015  { 0, Xapian::Query::OP_NEAR },
1016  { 0, Xapian::Query::OP_PHRASE },
1019  { 6, Xapian::Query::OP_MAX }
1020  };
1021  static const char* terms[] = {"two", "all", "paragraph", "banana"};
1022  Xapian::Enquire enquire(db);
1023  for (auto& test : tests) {
1024  Xapian::Query::op op = test.op;
1025  Xapian::doccount hits = test.hits;
1026  tout << op << " should give " << hits << " hits\n";
1027  Xapian::Query query(op, terms, terms + 4);
1028  enquire.set_query(query);
1029  Xapian::MSet mset = enquire.get_mset(0, 10);
1030  TEST_EQUAL(mset.get_matches_estimated(), hits);
1031  TEST_EQUAL(mset.get_matches_upper_bound(), hits);
1032  TEST_EQUAL(mset.get_matches_lower_bound(), hits);
1033  }
1034 }
1035 
1037 // Regression test for bug fixed in 1.3.1 and 1.2.11.
1038 DEFINE_TESTCASE(stubdb7, !backend) {
1040  Xapian::Database("nosuchdirectory", Xapian::DB_BACKEND_STUB));
1042  Xapian::WritableDatabase("nosuchdirectory",
1044 }
1045 
1047 // This runs for multi_* too, so serves to check that we get the same weights
1048 // with multiple databases as without.
1049 DEFINE_TESTCASE(msetweights1, backend) {
1050  Xapian::Database db = get_database("apitest_simpledata");
1051  Xapian::Enquire enq(db);
1053  Xapian::Query("paragraph"),
1054  Xapian::Query("word"));
1055  enq.set_query(q);
1056  // 5 documents match, and the 4th and 5th have the same weight, so ask for
1057  // 4 as that's a good test that we get the right one in this case.
1058  Xapian::MSet mset = enq.get_mset(0, 4);
1059 
1060  static const struct { Xapian::docid did; double wt; } expected[] = {
1061  { 2, 1.2058248004573934864 },
1062  { 4, 0.81127876655507624726 },
1063  { 1, 0.17309550762546158098 },
1064  { 3, 0.14609528172558261527 }
1065  };
1066 
1067  TEST_EQUAL(mset.size(), sizeof(expected) / sizeof(expected[0]));
1068  for (Xapian::doccount i = 0; i < mset.size(); ++i) {
1069  TEST_EQUAL(*mset[i], expected[i].did);
1070  TEST_EQUAL_DOUBLE(mset[i].get_weight(), expected[i].wt);
1071  }
1072 
1073  // Now test a query which matches only even docids, so in the multi case
1074  // one subdatabase doesn't match.
1075  enq.set_query(Xapian::Query("one"));
1076  mset = enq.get_mset(0, 3);
1077 
1078  static const struct { Xapian::docid did; double wt; } expected2[] = {
1079  { 6, 0.73354729848273669823 },
1080  { 2, 0.45626501034348893038 }
1081  };
1082 
1083  TEST_EQUAL(mset.size(), sizeof(expected2) / sizeof(expected2[0]));
1084  for (Xapian::doccount i = 0; i < mset.size(); ++i) {
1085  TEST_EQUAL(*mset[i], expected2[i].did);
1086  TEST_EQUAL_DOUBLE(mset[i].get_weight(), expected2[i].wt);
1087  }
1088 }
1089 
1090 DEFINE_TESTCASE(itorskiptofromend1, backend) {
1091  Xapian::Database db = get_database("apitest_simpledata");
1092 
1094  t.skip_to("zzzzz");
1095  TEST(t == db.termlist_end(1));
1096  // This worked in 1.2.x but segfaulted in 1.3.1.
1097  t.skip_to("zzzzzz");
1098 
1100  p.skip_to(99999);
1101  TEST(p == db.postlist_end("one"));
1102  // This segfaulted prior to 1.3.2.
1103  p.skip_to(999999);
1104 
1106  i.skip_to(99999);
1107  TEST(i == db.positionlist_end(6, "one"));
1108  // This segfaulted prior to 1.3.2.
1109  i.skip_to(999999);
1110 
1112  v.skip_to(99999);
1113  TEST(v == db.valuestream_end(1));
1114  // These segfaulted prior to 1.3.2.
1115  v.skip_to(999999);
1116  v.check(9999999);
1117 }
1118 
1120 // Regression test for bug fixed in 1.2.17 and 1.3.2 - the size gets fixed
1121 // but the uncorrected size was passed to the base file. Also, abort() was
1122 // called on 0.
1123 DEFINE_TESTCASE(blocksize1, chert || glass) {
1124  string db_dir = "." + get_dbtype();
1125  mkdir(db_dir.c_str(), 0755);
1126  db_dir += "/db__blocksize1";
1127  int flags;
1128  if (get_dbtype() == "chert") {
1130  } else {
1132  }
1133  static const unsigned bad_sizes[] = {
1134  65537, 8000, 2000, 1024, 16, 7, 3, 1, 0
1135  };
1136  for (size_t i = 0; i < sizeof(bad_sizes) / sizeof(bad_sizes[0]); ++i) {
1137  size_t block_size = bad_sizes[i];
1138  rm_rf(db_dir);
1139  Xapian::WritableDatabase db(db_dir, flags, block_size);
1140  Xapian::Document doc;
1141  doc.add_term("XYZ");
1142  doc.set_data("foo");
1143  db.add_document(doc);
1144  db.commit();
1145  }
1146 }
1147 
1149 DEFINE_TESTCASE(notermlist1, glass) {
1150  string db_dir = "." + get_dbtype();
1151  mkdir(db_dir.c_str(), 0755);
1152  db_dir += "/db__notermlist1";
1154  if (get_dbtype() == "chert") {
1155  flags |= Xapian::DB_BACKEND_CHERT;
1156  } else {
1157  flags |= Xapian::DB_BACKEND_GLASS;
1158  }
1159  rm_rf(db_dir);
1160  Xapian::WritableDatabase db(db_dir, flags);
1161  Xapian::Document doc;
1162  doc.add_term("hello");
1163  doc.add_value(42, "answer");
1164  db.add_document(doc);
1165  db.commit();
1166  TEST(!file_exists(db_dir + "/termlist.glass"));
1168 }
1169 
1171 DEFINE_TESTCASE(newfreelistblock1, writable) {
1172  Xapian::Document doc;
1173  doc.add_term("foo");
1174  for (int i = 100; i < 120; ++i) {
1175  doc.add_term(str(i));
1176  }
1177 
1179  for (int j = 0; j < 50; ++j) {
1180  wdb.add_document(doc);
1181  }
1182  wdb.commit();
1183 
1184  for (int k = 0; k < 1000; ++k) {
1185  wdb.add_document(doc);
1186  wdb.commit();
1187  }
1188 }
1189 
1195 DEFINE_TESTCASE(readonlyparentdir1, chert || glass) {
1196 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
1197  string path = get_named_writable_database_path("readonlyparentdir1");
1198  // Fix permissions if the previous test was killed.
1199  (void)chmod(path.c_str(), 0700);
1200  mkdir(path.c_str(), 0777);
1201  mkdir((path + "/sub").c_str(), 0777);
1202  Xapian::WritableDatabase db = get_named_writable_database("readonlyparentdir1/sub");
1203  TEST(chmod(path.c_str(), 0500) == 0);
1204  try {
1205  Xapian::Document doc;
1206  doc.add_term("hello");
1207  doc.set_data("some text");
1208  db.add_document(doc);
1209  db.commit();
1210  } catch (...) {
1211  // Attempt to fix the permissions, otherwise things like "rm -rf" on
1212  // the source tree will fail.
1213  (void)chmod(path.c_str(), 0700);
1214  throw;
1215  }
1216  TEST(chmod(path.c_str(), 0700) == 0);
1217 #endif
1218 }
1219 
1220 static void
1222 {
1223  Xapian::Document doc;
1224  doc.add_posting("hurricane", 199881);
1225  doc.add_posting("hurricane", 203084);
1226  doc.add_posting("katrina", 199882);
1227  doc.add_posting("katrina", 202473);
1228  doc.add_posting("katrina", 203085);
1229  db.add_document(doc);
1230 }
1231 
1233 DEFINE_TESTCASE(phrasebug1, positional) {
1234  Xapian::Database db = get_database("phrasebug1", make_phrasebug1_db);
1235  static const char * const qterms[] = { "katrina", "hurricane" };
1236  Xapian::Enquire e(db);
1237  Xapian::Query q(Xapian::Query::OP_PHRASE, qterms, qterms + 2, 5);
1238  e.set_query(q);
1239  Xapian::MSet mset = e.get_mset(0, 100);
1240  TEST_EQUAL(mset.size(), 0);
1241  static const char * const qterms2[] = { "hurricane", "katrina" };
1242  Xapian::Query q2(Xapian::Query::OP_PHRASE, qterms2, qterms2 + 2, 5);
1243  e.set_query(q2);
1244  mset = e.get_mset(0, 100);
1245  TEST_EQUAL(mset.size(), 1);
1246 }
1247 
1249 DEFINE_TESTCASE(retrylock1, writable && path) {
1250  // FIXME: Can't see an easy way to test this for remote databases - the
1251  // harness doesn't seem to provide a suitable way to reopen a remote.
1252 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR
1253  int fds[2];
1254  if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, PF_UNSPEC, fds) < 0) {
1255  FAIL_TEST("socketpair() failed");
1256  }
1257  if (fds[1] >= FD_SETSIZE)
1258  SKIP_TEST("socketpair() gave fd >= FD_SETSIZE");
1259  if (fcntl(fds[1], F_SETFL, O_NONBLOCK) < 0)
1260  FAIL_TEST("fcntl() failed to set O_NONBLOCK");
1261  pid_t child = fork();
1262  if (child == -1)
1263  FAIL_TEST("fork() failed");
1264  if (child == 0) {
1265  // Wait for signal that parent has opened the database.
1266  char ch;
1267  while (read(fds[0], &ch, 1) < 0) { }
1268 
1269  try {
1272  if (write(fds[0], "y", 1)) { }
1273  } catch (const Xapian::DatabaseLockError &) {
1274  if (write(fds[0], "l", 1)) { }
1275  } catch (const Xapian::Error &e) {
1276  const string & m = e.get_description();
1277  if (write(fds[0], m.data(), m.size())) { }
1278  } catch (...) {
1279  if (write(fds[0], "o", 1)) { }
1280  }
1281  _exit(0);
1282  }
1283 
1284  close(fds[0]);
1285 
1287  if (write(fds[1], "", 1) != 1)
1288  FAIL_TEST("Failed to signal to child process");
1289 
1290  char result[256];
1291  int r = read(fds[1], result, sizeof(result));
1292  if (r == -1) {
1293  if (errno == EAGAIN) {
1294  // Good.
1295  result[0] = 'y';
1296  } else {
1297  // Error.
1298  tout << "errno=" << errno << ": " << errno_to_string(errno) << '\n';
1299  result[0] = 'e';
1300  }
1301  r = 1;
1302  } else if (r >= 1) {
1303  if (result[0] == 'y') {
1304  // Child process managed to also get write lock!
1305  result[0] = '!';
1306  }
1307  } else {
1308  // EOF.
1309  result[0] = 'z';
1310  r = 1;
1311  }
1312 
1313  try {
1314  db.close();
1315  } catch (...) {
1316  kill(child, SIGKILL);
1317  int status;
1318  while (waitpid(child, &status, 0) < 0) {
1319  if (errno != EINTR) break;
1320  }
1321  throw;
1322  }
1323 
1324  if (result[0] == 'y') {
1325 retry:
1326  struct timeval tv;
1327  tv.tv_sec = 3;
1328  tv.tv_usec = 0;
1329  fd_set fdset;
1330  FD_ZERO(&fdset);
1331  FD_SET(fds[1], &fdset);
1332  int sr = select(fds[1] + 1, &fdset, NULL, NULL, &tv);
1333  if (sr == 0) {
1334  // Timed out.
1335  result[0] = 'T';
1336  r = 1;
1337  } else if (sr == -1) {
1338  if (errno == EINTR || errno == EAGAIN)
1339  goto retry;
1340  tout << "select() failed with errno=" << errno << ": "
1341  << errno_to_string(errno) << '\n';
1342  result[0] = 'S';
1343  r = 1;
1344  } else {
1345  r = read(fds[1], result, sizeof(result));
1346  if (r == -1) {
1347  // Error.
1348  tout << "read() failed with errno=" << errno << ": "
1349  << errno_to_string(errno) << '\n';
1350  result[0] = 'R';
1351  r = 1;
1352  } else if (r == 0) {
1353  // EOF.
1354  result[0] = 'Z';
1355  r = 1;
1356  }
1357  }
1358  }
1359 
1360  close(fds[1]);
1361 
1362  kill(child, SIGKILL);
1363  int status;
1364  while (waitpid(child, &status, 0) < 0) {
1365  if (errno != EINTR) break;
1366  }
1367 
1368  tout << string(result, r) << '\n';
1369  TEST_EQUAL(result[0], 'y');
1370 #endif
1371 }
1372 
1373 // Opening a WritableDatabase with low fds available - it should avoid them.
1374 DEFINE_TESTCASE(dbfilefd012, writable && !remote) {
1375 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
1376  int oldfds[3];
1377  for (int i = 0; i < 3; ++i) {
1378  oldfds[i] = dup(i);
1379  }
1380  try {
1381  for (int j = 0; j < 3; ++j) {
1382  close(j);
1383  TEST_REL(lseek(j, 0, SEEK_CUR), <, 0);
1384  TEST_EQUAL(errno, EBADF);
1385  }
1386 
1388 
1389  // Check we didn't use any of those low fds for tables, as that risks
1390  // data corruption if some other code in the same process tries to
1391  // write to them (see #651).
1392  for (int fd = 0; fd < 3; ++fd) {
1393  // Check that the fd is still closed, or isn't open O_RDWR (the
1394  // lock file gets opened O_WRONLY), or it's a pipe (if we're using
1395  // a child process to hold a non-OFD fcntl lock).
1396  int flags = fcntl(fd, F_GETFL);
1397  if (flags == -1) {
1398  TEST_EQUAL(errno, EBADF);
1399  } else if ((flags & O_ACCMODE) != O_RDWR) {
1400  // OK.
1401  } else {
1402  struct stat sb;
1403  TEST_NOT_EQUAL(fstat(fd, &sb), -1);
1404 #ifdef S_ISSOCK
1405  TEST(S_ISSOCK(sb.st_mode));
1406 #else
1407  // If we can't check it is a socket, at least check it is not a
1408  // regular file.
1409  TEST(!S_ISREG(sb.st_mode));
1410 #endif
1411  }
1412  }
1413  } catch (...) {
1414  for (int j = 0; j < 3; ++j) {
1415  dup2(oldfds[j], j);
1416  close(oldfds[j]);
1417  }
1418  throw;
1419  }
1420 
1421  for (int j = 0; j < 3; ++j) {
1422  dup2(oldfds[j], j);
1423  close(oldfds[j]);
1424  }
1425 #endif
1426 }
1427 
1429 DEFINE_TESTCASE(cursorbug1, writable && path) {
1432  Xapian::Enquire enq(db);
1434  Xapian::MSet mset;
1435  // The original problem triggers for chert and glass on repeat==7.
1436  for (int repeat = 0; repeat < 10; ++repeat) {
1437  tout.str(string());
1438  tout << "iteration #" << repeat << '\n';
1439 
1440  const int ITEMS = 10;
1441  int free_id = db.get_doccount();
1442  int offset = max(free_id, ITEMS * 2) - (ITEMS * 2);
1443  int limit = offset + (ITEMS * 2);
1444 
1445  mset = enq.get_mset(offset, limit);
1446  for (Xapian::MSetIterator m1 = mset.begin(); m1 != mset.end(); ++m1) {
1447  (void)m1.get_document().get_value(0);
1448  }
1449 
1450  for (int i = free_id; i <= free_id + ITEMS; ++i) {
1451  Xapian::Document doc;
1452  const string & id = str(i);
1453  string qterm = "Q" + id;
1454  doc.add_value(0, id);
1455  doc.add_boolean_term(qterm);
1456  wdb.replace_document(qterm, doc);
1457  }
1458  wdb.commit();
1459 
1460  db.reopen();
1461  mset = enq.get_mset(offset, limit);
1462  for (Xapian::MSetIterator m2 = mset.begin(); m2 != mset.end(); ++m2) {
1463  (void)m2.get_document().get_value(0);
1464  }
1465  }
1466 }
1467 
1468 // Regression test for #674, fixed in 1.2.21 and 1.3.3.
1469 DEFINE_TESTCASE(sortvalue2, backend) {
1470  Xapian::Database db = get_database("apitest_simpledata");
1471  db.add_database(get_database("apitest_simpledata2"));
1472  Xapian::Enquire enq(db);
1474  enq.set_sort_by_value(0, false);
1475  Xapian::MSet mset = enq.get_mset(0, 50);
1476 
1477  // Check all results are in key order - the bug was that they were sorted
1478  // by docid instead with multiple remote databases.
1479  string old_key;
1480  for (Xapian::MSetIterator i = mset.begin(); i != mset.end(); ++i) {
1481  string key = db.get_document(*i).get_value(0);
1482  TEST(old_key <= key);
1483  swap(old_key, key);
1484  }
1485 }
1486 
1488 DEFINE_TESTCASE(enquiregetquery1, backend) {
1489  Xapian::Database db = get_database("apitest_simpledata");
1490  Xapian::Enquire enq(db);
1491  TEST_EQUAL(enq.get_query().get_description(), "Query()");
1492 }
1493 
1494 DEFINE_TESTCASE(embedded1, singlefile) {
1495  // In reality you should align the embedded database to a multiple of
1496  // database block size, but any offset is meant to work.
1497  off_t offset = 1234;
1498 
1499  Xapian::Database db = get_database("apitest_simpledata");
1500  const string & db_path = get_database_path("apitest_simpledata");
1501  const string & tmp_path = db_path + "-embedded";
1502  ofstream out(tmp_path, fstream::trunc|fstream::binary);
1503  out.seekp(offset);
1504  out << ifstream(db_path, fstream::binary).rdbuf();
1505  out.close();
1506 
1507  {
1508  int fd = open(tmp_path.c_str(), O_RDONLY|O_BINARY);
1509  lseek(fd, offset, SEEK_SET);
1510  Xapian::Database db_embedded(fd);
1511  TEST_EQUAL(db.get_doccount(), db_embedded.get_doccount());
1512  }
1513 
1514  {
1515  int fd = open(tmp_path.c_str(), O_RDONLY|O_BINARY);
1516  lseek(fd, offset, SEEK_SET);
1517  size_t check_errors =
1519  TEST_EQUAL(check_errors, 0);
1520  }
1521 }
1522 
1524 DEFINE_TESTCASE(exactxor1, backend) {
1525  Xapian::Database db = get_database("apitest_simpledata");
1526  Xapian::Enquire enq(db);
1527 
1528  static const char * const words[4] = {
1529  "blank", "test", "paragraph", "banana"
1530  };
1531  Xapian::Query q(Xapian::Query::OP_XOR, words, words + 4);
1532  enq.set_query(q);
1534  Xapian::MSet mset = enq.get_mset(0, 0);
1535  // A reversed conditional gave us 5 in this case.
1537  // Test improved lower bound in 1.3.7 (earlier versions gave 0).
1539 
1540  static const char * const words2[4] = {
1541  "queri", "test", "paragraph", "word"
1542  };
1543  Xapian::Query q2(Xapian::Query::OP_XOR, words2, words2 + 4);
1544  enq.set_query(q2);
1546  mset = enq.get_mset(0, 0);
1547  // A reversed conditional gave us 6 in this case.
1549  // Test improved lower bound in 1.3.7 (earlier versions gave 0).
1551 }
1552 
1554 DEFINE_TESTCASE(getrevision1, chert || glass) {
1556  TEST_EQUAL(db.get_revision(), 0);
1557  db.commit();
1558  TEST_EQUAL(db.get_revision(), 0);
1559  Xapian::Document doc;
1560  doc.add_term("hello");
1561  db.add_document(doc);
1562  TEST_EQUAL(db.get_revision(), 0);
1563  db.commit();
1564  TEST_EQUAL(db.get_revision(), 1);
1565  db.commit();
1566  TEST_EQUAL(db.get_revision(), 1);
1567  db.add_document(doc);
1568  db.commit();
1569  TEST_EQUAL(db.get_revision(), 2);
1570 }
1571 
1573 DEFINE_TESTCASE(getrevision2, !backend) {
1574  Xapian::Database db;
1575  TEST_EQUAL(db.get_revision(), 0);
1576  Xapian::Database wdb;
1577  TEST_EQUAL(wdb.get_revision(), 0);
1578 }
1579 
1581 DEFINE_TESTCASE(getdocumentlazy1, backend) {
1582  Xapian::Database db = get_database("apitest_simpledata");
1584  Xapian::Document doc = db.get_document(2);
1585  TEST_EQUAL(doc.get_data(), doc_lazy.get_data());
1586  TEST_EQUAL(doc.get_value(0), doc_lazy.get_value(0));
1587 }
1588 
1590 DEFINE_TESTCASE(getdocumentlazy2, backend) {
1591  Xapian::Database db = get_database("apitest_simpledata");
1592  Xapian::Document doc;
1593  try {
1595  } catch (const Xapian::DocNotFoundError&) {
1596  // DOC_ASSUME_VALID is really just a hint, so ignoring is OK (the
1597  // remote backend currently does).
1598  }
1599  TEST(doc.get_data().empty());
1601  doc = db.get_document(db.get_lastdocid() + 1);
1602  );
1603 }
1604 
1605 static void
1607 {
1608  Xapian::Document doc;
1609  doc.add_term("foo");
1610  doc.add_boolean_term("bar");
1611  db.add_document(doc);
1612  Xapian::Document doc2;
1613  doc2.add_posting("foo", 0, 2);
1614  doc2.add_term("foo2");
1615  doc2.add_boolean_term("baz");
1616  doc2.add_boolean_term("baz2");
1617  db.add_document(doc2);
1618 }
1619 
1620 DEFINE_TESTCASE(getuniqueterms1, backend) {
1621  Xapian::Database db =
1622  get_database("uniqterms_gt_doclen", gen_uniqterms_gt_doclen_db);
1623 
1624  auto unique1 = db.get_unique_terms(1);
1625  TEST_REL(unique1, <=, db.get_doclength(1));
1626  TEST_REL(unique1, <, db.get_document(1).termlist_count());
1627  // Ideally it'd be equal to 1, and in this case it is, but the current
1628  // backends can't always efficiently ensure an exact answer.
1629  TEST_REL(unique1, >=, 1);
1630 
1631  auto unique2 = db.get_unique_terms(2);
1632  TEST_REL(unique2, <=, db.get_doclength(2));
1633  TEST_REL(unique2, <, db.get_document(2).termlist_count());
1634  // Ideally it'd be equal to 2, but the current backends can't always
1635  // efficiently ensure an exact answer and here it is actually 3.
1636  TEST_REL(unique2, >=, 2);
1637 }
1638 
1645 DEFINE_TESTCASE(nopositionbug1, backend) {
1646  Xapian::Database db =
1647  get_database("uniqterms_gt_doclen", gen_uniqterms_gt_doclen_db);
1648 
1649  // Test both orders.
1650  static const char* const terms1[] = { "foo", "baz" };
1651  static const char* const terms2[] = { "baz", "foo" };
1652 
1653  Xapian::Enquire enq(db);
1655  begin(terms1), end(terms1), 10));
1656  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1657 
1659  begin(terms2), end(terms2), 10));
1660  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1661 
1663  begin(terms1), end(terms1), 10));
1664  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1665 
1667  begin(terms2), end(terms2), 10));
1668  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1669 
1670  // Exercise exact phrase case too.
1672  begin(terms1), end(terms1), 2));
1673  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1674 
1676  begin(terms2), end(terms2), 2));
1677  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1678 }
1679 
1684 DEFINE_TESTCASE(checkatleast4, backend) {
1685  Xapian::Database db = get_database("apitest_simpledata");
1686  Xapian::Enquire enq(db);
1687  enq.set_query(Xapian::Query("paragraph"));
1688  // This used to cause access to an element in an empty vector.
1689  Xapian::MSet mset = enq.get_mset(0, 0, 4);
1690  TEST_EQUAL(mset.size(), 0);
1691 }
1692 
1694 DEFINE_TESTCASE(freelistleak1, check) {
1695  auto path = get_database_path("freelistleak1",
1696  [](Xapian::WritableDatabase& wdb,
1697  const string&)
1698  {
1699  wdb.set_metadata("foo", "bar");
1700  wdb.commit();
1701  Xapian::Document doc;
1702  doc.add_term("baz");
1703  wdb.add_document(doc);
1704  });
1705  size_t check_errors =
1707  TEST_EQUAL(check_errors, 0);
1708 }
1709 
1711 DEFINE_TESTCASE(splitpostings1, writable) {
1713  Xapian::Document doc;
1714  // Add postings to create a split internally.
1715  for (Xapian::termpos pos = 0; pos <= 100; pos += 10) {
1716  doc.add_posting("foo", pos);
1717  }
1718  for (Xapian::termpos pos = 5; pos <= 100; pos += 20) {
1719  doc.add_posting("foo", pos);
1720  }
1721  db.add_document(doc);
1722  db.commit();
1723 
1724  Xapian::termpos expect = 0;
1725  Xapian::termpos pos = 0;
1726  for (auto p = db.positionlist_begin(1, "foo");
1727  p != db.positionlist_end(1, "foo"); ++p) {
1728  TEST_REL(expect, <=, 100);
1729  pos = *p;
1730  TEST_EQUAL(pos, expect);
1731  expect += 5;
1732  if (expect % 20 == 15) expect += 5;
1733  }
1734  TEST_EQUAL(pos, 100);
1735 }
1736 
1738 DEFINE_TESTCASE(multidb1, backend) {
1739  Xapian::Database db;
1740  TEST_EQUAL(db.size(), 0);
1741  Xapian::Database db2 = get_database("apitest_simpledata");
1742  TEST(db2.size() != 0);
1743  db.add_database(db2);
1744  TEST_EQUAL(db.size(), db2.size());
1745  db.add_database(db2);
1746  // Regression test for bug introduced and fixed in git master before 1.5.0.
1747  // Adding a multi database to an empty database incorrectly worked just
1748  // like assigning the database object. The list of shards is now copied
1749  // instead.
1750  TEST_EQUAL(db.size(), db2.size() * 2);
1752  TEST_EQUAL(db.size(), db2.size() * 2);
1753 }
1754 
1755 // Test that all the terms returned exist.
1756 DEFINE_TESTCASE(allterms7, backend) {
1757  Xapian::Database db = get_database("etext");
1758  for (auto i = db.allterms_begin(); i != db.allterms_end(); ++i) {
1759  string term = *i;
1760  TEST(db.get_termfreq(term) > 0);
1761  TEST(db.postlist_begin(term) != db.postlist_end(term));
1762  }
1763 }
1764 
1765 /* Test searching for non-existent terms returns zero results.
1766  *
1767  * Regression test for GlassTable::readahead_key() throwing "Key too long"
1768  * error if passed an oversized key.
1769  */
1770 DEFINE_TESTCASE(nosuchterm, backend) {
1771  Xapian::Database db = get_database("apitest_simpledata");
1772  Xapian::Enquire enquire{db};
1773  // Test up to a length longer than any backend supports.
1774  const unsigned MAX_LEN = 300;
1775  string term;
1776  term.reserve(MAX_LEN);
1777  while (term.size() < MAX_LEN) {
1778  term += 'x';
1779  enquire.set_query(Xapian::Query(term));
1780  TEST_EQUAL(enquire.get_mset(0, 10).size(), 0);
1781  }
1782 }
1783 
1784 // Test exception for check() on remote via stub.
1785 DEFINE_TESTCASE(unsupportedcheck1, path) {
1786  mkdir(".stub", 0755);
1787  const char* stubpath = ".stub/unsupportedcheck1";
1788  ofstream out(stubpath);
1789  TEST(out.is_open());
1790  out << "remote :" << BackendManager::get_xapian_progsrv_command()
1791  << ' ' << get_database_path("apitest_simpledata") << '\n';
1792  out.close();
1793 
1795  Xapian::Database::check(stubpath));
1796 }
1797 
1798 // Test exception for check() on inmemory via stub.
1799 DEFINE_TESTCASE(unsupportedcheck2, inmemory) {
1800  mkdir(".stub", 0755);
1801  const char* stubpath = ".stub/unsupportedcheck2";
1802  ofstream out(stubpath);
1803  TEST(out.is_open());
1804  out << "inmemory\n";
1805  out.close();
1806 
1808  Xapian::Database::check(stubpath));
1809 }
1810 
1811 // Test exception for passing empty filename to check().
1812 DEFINE_TESTCASE(unsupportedcheck3, !backend) {
1813  // Regression test, exception was DatabaseOpeningError with description:
1814  // Failed to rewind file descriptor -1 (Bad file descriptor)
1815  try {
1816  Xapian::Database::check(string());
1817  } catch (const Xapian::DatabaseOpeningError& e) {
1818  string enoent_msg = errno_to_string(ENOENT);
1819  TEST_EQUAL(e.get_error_string(), enoent_msg);
1820  }
1821 }
1822 
1823 // Test handling of corrupt DB with out of range values in the version file.
1824 // Regression test for #824, fixed in 1.4.25.
1825 DEFINE_TESTCASE(corruptglass1, glass) {
1826  string db_path =
1827  test_driver::get_srcdir() + "/testdata/glass_corrupt_db1";
1828 
1829  for (int n = '1'; n <= '3'; ++n) {
1830  db_path.back() = n;
1832  Xapian::Database db(db_path));
1833 
1835  Xapian::Database::check(db_path));
1836  }
1837 }
static const char * get_xapian_progsrv_command()
Get the command line required to run xapian-progsrv.
int close(FD &fd)
Definition: fd.h:63
Xapian::doccount size() const
Return number of items in this MSet object.
Definition: omenquire.cc:318
Xapian::Document get_document(Xapian::docid did) const
Get a document from the database, given its document id.
Definition: omdatabase.cc:490
Xapian::docid add_document(const Xapian::Document &document)
Add a new document to the database.
Definition: omdatabase.cc:902
PositionIterator positionlist_end(Xapian::docid, const std::string &) const
Corresponding end iterator to positionlist_begin().
Definition: database.h:254
Run multiple tests for different backends.
void add_value(Xapian::valueno slot, const std::string &value)
Add a new value.
Definition: omdocument.cc:107
static size_t check(const std::string &path, int opts=0, std::ostream *out=NULL)
Check the integrity of a database or database table.
Definition: database.h:564
TermIterator termlist_begin(Xapian::docid did) const
An iterator pointing to the start of the termlist for a given document.
Definition: omdatabase.cc:198
Indicates an attempt to access a closed database.
Definition: error.h:1097
#define TEST(a)
Test a condition, without an additional explanation for failure.
Definition: testsuite.h:275
void skip_to(Xapian::termpos termpos)
Advance the iterator to term position termpos.
This class is used to access a database, or a group of databases.
Definition: database.h:68
void set_sort_by_value(Xapian::valueno sort_key, bool reverse)
Set the sorting to be by value only.
Definition: omenquire.cc:869
Xapian::termcount termlist_count() const
The length of the termlist - i.e.
Definition: omdocument.cc:191
static const Xapian::Query MatchAll
A query matching all documents.
Definition: query.h:75
Match documents which an odd number of subqueries match.
Definition: query.h:107
const int DB_CREATE
Create a new database.
Definition: constants.h:44
DatabaseOpeningError indicates failure to open a database.
Definition: error.h:581
PositionIterator positionlist_begin(Xapian::docid did, const std::string &tname) const
An iterator pointing to the start of the position list for a given term in a given document...
Definition: omdatabase.cc:250
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:46
op
Query operators.
Definition: query.h:78
Xapian::termcount get_doclength_lower_bound() const
Get a lower bound on the length of a document in this DB.
Definition: omdatabase.cc:401
Xapian::doccount get_matches_lower_bound() const
Lower bound on the total number of matching documents.
Definition: omenquire.cc:246
TermIterator allterms_end(const std::string &=std::string()) const
Corresponding end iterator to allterms_begin(prefix).
Definition: database.h:269
Xapian::WritableDatabase get_writable_database(const string &dbname)
Definition: apitest.cc:87
include <sys/socket.h> with portability workarounds.
Xapian::docid get_lastdocid() const
Get the highest document id which has been used in the database.
Definition: omdatabase.cc:279
Convert errno value to std::string, thread-safe if possible.
Build a Xapian::Query object from a user query string.
Definition: queryparser.h:778
#define O_BINARY
Definition: safefcntl.h:81
a generic test suite engine
bool reopen()
Re-open the database.
Definition: omdatabase.cc:125
C++ function versions of useful Unix commands.
WritableDatabase open()
Construct a WritableDatabase object for a new, empty InMemory database.
Definition: dbfactory.h:104
Class for iterating over document values.
Definition: valueiterator.h:40
Class representing a list of search results.
Definition: mset.h:44
void skip_to(const std::string &term)
Advance the iterator to term term.
Xapian::WritableDatabase get_writable_database_again()
Definition: apitest.cc:125
STL namespace.
Pick the maximum weight of any subquery.
Definition: query.h:249
MSet get_mset(Xapian::doccount first, Xapian::doccount maxitems, Xapian::doccount checkatleast=0, const RSet *omrset=0, const MatchDecider *mdecider=0) const
Get (a portion of) the match set for the current query.
Definition: omenquire.cc:932
Convert types to std::string.
static double est(double l, double r, double n)
Definition: orpostlist.cc:306
const int DB_CREATE_OR_OPEN
Create database if it doesn&#39;t already exist.
Definition: constants.h:35
Utility functions for testing files.
Xapian::rev get_revision() const
Get the revision of the database.
Definition: omdatabase.cc:805
void replace_document(Xapian::docid did, const Xapian::Document &document)
Replace a given document in the database.
Definition: omdatabase.cc:952
void set_metadata(const std::string &key, const std::string &metadata)
Set the user-specified metadata associated with a given key.
Definition: omdatabase.cc:1064
#define false
Definition: header.h:9
void clear_terms()
Remove all terms (and postings) from the document.
Definition: omdocument.cc:184
Xapian::doccount get_doccount() const
Get the number of documents in the database.
Definition: omdatabase.cc:267
const int DOC_ASSUME_VALID
Assume document id is valid.
Definition: constants.h:280
DEFINE_TESTCASE(lockfileumask1, chert||glass)
Regression test - lockfile should honour umask, was only user-readable.
Definition: api_backend.cc:57
Xapian::totallength get_total_length() const
Get the total length of all the documents in the database.
Definition: omdatabase.cc:312
static std::string get_srcdir()
Read srcdir from environment and if not present, make a valiant attempt to guess a value...
Definition: testsuite.cc:128
include <sys/stat.h> with portability enhancements
#define SOCK_CLOEXEC
Definition: safesyssocket.h:83
static void make_msize2_db(Xapian::WritableDatabase &db, const string &)
Definition: api_backend.cc:655
const int DB_BACKEND_GLASS
Use the glass backend.
Definition: constants.h:158
docid get_docid() const
Get the document id which is associated with this document (if any).
Definition: omdocument.cc:220
Xapian::WritableDatabase get_named_writable_database(const std::string &name, const std::string &source)
Definition: apitest.cc:93
Enable partial matching.
Definition: queryparser.h:837
test functionality of the Xapian API
void rm_rf(const string &filename)
Remove a directory and contents, just like the Unix "rm -rf" command.
Definition: unixcmds.cc:111
Xapian::doccount get_matches_upper_bound() const
Upper bound on the total number of matching documents.
Definition: omenquire.cc:262
Xapian::doclength get_avlength() const
Get the average length of the documents in the database.
Definition: omdatabase.cc:293
static void make_xordecay1_db(Xapian::WritableDatabase &db, const string &)
Definition: api_backend.cc:703
Class for iterating over a list of terms.
Definition: termiterator.h:41
unsigned XAPIAN_TERMCOUNT_BASE_TYPE termcount
A counts of terms.
Definition: types.h:72
#define TEST_REL(A, REL, B)
Test a relation holds,e.g. TEST_REL(a,>,b);.
Definition: testmacros.h:32
Class for iterating over a list of terms.
#define TEST_NOT_EQUAL(a, b)
Test for non-equality of two things.
Definition: testsuite.h:305
InvalidArgumentError indicates an invalid parameter value was passed to the API.
Definition: error.h:241
Xapian::termcount get_doclength_upper_bound() const
Get an upper bound on the length of a document in this DB.
Definition: omdatabase.cc:421
Base class for backend handling in test harness.
string get_database_path(const string &dbname)
Definition: apitest.cc:72
Class implementing a "boolean" weighting scheme.
Definition: weight.h:422
Xapian::doccount get_firstitem() const
Rank of first item in this MSet.
Definition: omenquire.cc:239
ValueIterator valuestream_end(Xapian::valueno) const
Return end iterator corresponding to valuestream_begin().
Definition: database.h:363
std::string get_named_writable_database_path(const std::string &name)
Definition: apitest.cc:99
DatabaseModifiedError indicates a database was modified.
Definition: error.h:539
DatabaseLockError indicates failure to lock a database.
Definition: error.h:493
Pick the best N subqueries and combine with OP_OR.
Definition: query.h:215
const int DB_OPEN
Open an existing database.
Definition: constants.h:50
This class provides read/write access to a database.
Definition: database.h:789
static void make_phrasebug1_db(Xapian::WritableDatabase &db, const string &)
std::ostringstream tout
The debug printing stream.
Definition: testsuite.cc:103
void errno_to_string(int e, string &s)
Iterator over a Xapian::MSet.
Definition: mset.h:368
Match only documents where all subqueries match near and in order.
Definition: query.h:152
Indicates an attempt to use a feature which is unavailable.
Definition: error.h:719
Match the first subquery taking extra weight from other subqueries.
Definition: query.h:118
Public interfaces for the Xapian library.
bool operator()(const Xapian::Document &) const
Decide whether we want this document to be in the MSet.
Definition: api_backend.cc:423
Match like OP_AND but only taking weight from the first subquery.
Definition: query.h:128
bool locked() const
Test if this database is currently locked for writing.
Definition: omdatabase.cc:793
#define S_ISREG(ST_MODE)
Definition: safesysstat.h:60
void delete_document(Xapian::docid did)
Delete a document from the database.
Definition: omdatabase.cc:925
#define TEST_EXCEPTION(TYPE, CODE)
Check that CODE throws exactly Xapian exception TYPE.
Definition: testutils.h:109
bool was_called() const
Definition: api_backend.cc:428
Match only documents where a value slot is within a given range.
Definition: query.h:158
std::string get_dbtype()
Definition: apitest.cc:42
string str(int value)
Convert int to std::string.
Definition: str.cc:90
MSetIterator begin() const
Return iterator pointing to the first item in this MSet.
Definition: mset.h:624
MSetIterator end() const
Return iterator pointing to just after the last item in this MSet.
Definition: mset.h:629
Xapian::termcount get_doclength(Xapian::docid did) const
Get the length of a document.
Definition: omdatabase.cc:461
void commit()
Commit any pending modifications made to the database.
Definition: omdatabase.cc:857
Xapian::Weight subclass implementing the traditional probabilistic formula.
Definition: weight.h:763
Xapian::Database get_writable_database_as_database()
Definition: apitest.cc:119
Class for iterating over term positions.
static const test_desc tests[]
The lists of tests to perform.
Indicates an attempt to access a database not present.
Definition: error.h:1055
Query parse_query(const std::string &query_string, unsigned flags=FLAG_DEFAULT, const std::string &default_prefix=std::string())
Parse a query.
Definition: queryparser.cc:161
TermIterator allterms_begin(const std::string &prefix=std::string()) const
An iterator which runs across all terms with a given prefix.
Definition: omdatabase.cc:223
TermIterator termlist_end(Xapian::docid) const
Corresponding end iterator to termlist_begin().
Definition: database.h:240
#define TEST_EQUAL_DOUBLE(a, b)
Test two doubles for near equality.
Definition: testsuite.h:295
#define SKIP_TEST_FOR_BACKEND(B)
Definition: apitest.h:75
ValueIterator valuestream_begin(Xapian::valueno slot) const
Return an iterator over the value in slot slot for each document.
Definition: omdatabase.cc:450
void add_database(const Database &database)
Add an existing database (or group of databases) to those accessed by this object.
Definition: omdatabase.cc:148
void set_query(const Xapian::Query &query, Xapian::termcount qlen=0)
Set the query to run.
Definition: omenquire.cc:793
Indicates an attempt to access a document not present in the database.
Definition: error.h:674
size_t size() const
Return number of shards in this Database object.
Definition: database.h:93
Match like OP_OR but weighting as if a single term.
Definition: query.h:239
DatabaseCorruptError indicates database corruption was detected.
Definition: error.h:409
std::string get_description() const
Return a string describing this object.
Definition: error.cc:93
Base class for matcher decision functor.
Definition: enquire.h:118
bool check(Xapian::docid docid)
Check if the specified docid occurs.
void add_posting(const std::string &tname, Xapian::termpos tpos, Xapian::termcount wdfinc=1)
Add an occurrence of a term at a particular position.
Definition: omdocument.cc:128
#define FAIL_TEST(MSG)
Fail the current testcase with message MSG.
Definition: testsuite.h:68
static void gen_uniqterms_gt_doclen_db(Xapian::WritableDatabase &db, const string &)
Match only documents which all subqueries match.
Definition: query.h:84
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:63
Xapian::Database get_database(const string &dbname)
Definition: apitest.cc:48
Xapian::doccount get_matches_estimated() const
Estimate of the total number of matching documents.
Definition: omenquire.cc:253
const Xapian::Query & get_query() const
Get the current query.
Definition: omenquire.cc:800
const int DB_BACKEND_STUB
Open a stub database file.
Definition: constants.h:179
void set_database(const Database &db)
Specify the database being searched.
Definition: queryparser.cc:141
#define SKIP_TEST(MSG)
Skip the current testcase with message MSG.
Definition: testsuite.h:74
std::string get_description() const
Return a string describing this object.
Definition: query.cc:232
This class provides an interface to the information retrieval system for the purpose of searching...
Definition: enquire.h:152
unsigned XAPIAN_DOCID_BASE_TYPE doccount
A count of documents.
Definition: types.h:38
Match only documents where all subqueries match near each other.
Definition: query.h:140
void flush()
Pre-1.1.0 name for commit().
Definition: database.h:951
const char * get_error_string() const
Returns any system error string associated with this exception.
Definition: error.cc:50
All exceptions thrown by Xapian are subclasses of Xapian::Error.
Definition: error.h:43
Match documents which the first subquery matches but no others do.
Definition: query.h:99
Match documents which at least one subquery matches.
Definition: query.h:92
void skip_to(Xapian::docid did)
Advance the iterator to document did.
Xapian-specific test helper functions and macros.
unsigned XAPIAN_TERMPOS_BASE_TYPE termpos
A term position within a document or query.
Definition: types.h:83
static const Xapian::Query MatchNothing
A query matching no documents.
Definition: query.h:65
const int DB_NO_TERMLIST
When creating a database, don&#39;t create a termlist table.
Definition: constants.h:136
<unistd.h>, but with compat.
include <sys/wait.h>, with portability stuff.
void add_boolean_term(const std::string &term)
Add a boolean filter term to the document.
Definition: document.h:192
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:225
static void make_msize1_db(Xapian::WritableDatabase &db, const string &)
Definition: api_backend.cc:605
const int DB_RETRY_LOCK
If the database is already locked, retry the lock.
Definition: constants.h:145
void set_weighting_scheme(const Weight &weight_)
Set the weighting scheme to use for queries.
Definition: omenquire.cc:819
unsigned XAPIAN_DOCID_BASE_TYPE docid
A unique identifier for a document.
Definition: types.h:52
Class representing a query.
Definition: query.h:46
std::string get_data() const
Get data stored in the document.
Definition: omdocument.cc:71
#define TEST_EQUAL(a, b)
Test for equality of two things.
Definition: testsuite.h:278
Xapian::termcount get_unique_terms(Xapian::docid did) const
Get the number of unique terms in document.
Definition: omdatabase.cc:476
PostingIterator postlist_end(const std::string &) const
Corresponding end iterator to postlist_begin().
Definition: database.h:225
Xapian::Weight subclass implementing the Language Model formula.
Definition: weight.h:1401
static void make_ordecay_db(Xapian::WritableDatabase &db, const string &)
Definition: api_backend.cc:731
void set_data(const std::string &data)
Set data stored in the document.
Definition: omdocument.cc:78
bool file_exists(const char *path)
Test if a file exists.
Definition: filetests.h:39
void set_collapse_key(Xapian::valueno collapse_key, Xapian::doccount collapse_max=1)
Set the collapse key to use for queries.
Definition: omenquire.cc:842
std::string get_value(Xapian::valueno slot) const
Get value by number.
Definition: omdocument.cc:64
virtual void close()
Close the database.
Definition: omdatabase.cc:138
include <fcntl.h>, but working around broken platforms.
Xapian::doccount get_termfreq(const std::string &tname) const
Get the number of documents in the database indexed by a given term.
Definition: omdatabase.cc:323
A handle representing a document in a Xapian database.
Definition: document.h:61
const int DB_BACKEND_CHERT
Use the chert backend.
Definition: constants.h:170
Xapian::Weight subclass implementing the BM25 probabilistic formula.
Definition: weight.h:535
UnimplementedError indicates an attempt to use an unimplemented feature.
Definition: error.h:325
PostingIterator postlist_begin(const std::string &tname) const
An iterator pointing to the start of the postlist for a given term.
Definition: omdatabase.cc:162
void skip_to(Xapian::docid docid_or_slot)
Advance the iterator to document id or value slot docid_or_slot.
void add_term(const std::string &tname, Xapian::termcount wdfinc=1)
Add a term to the document, without positional information.
Definition: omdocument.cc:140
Xapian::termcount get_collection_freq(const std::string &tname) const
Return the total number of occurrences of the given term.
Definition: omdatabase.cc:339
Xapian::termcount get_wdf_upper_bound(const std::string &term) const
Get an upper bound on the wdf of term term.
Definition: omdatabase.cc:435
static void make_orcheck_db(Xapian::WritableDatabase &db, const string &)
Definition: api_backend.cc:786
const int DBCHECK_SHOW_STATS
Show statistics for the B-tree.
Definition: constants.h:228