xapian-core  1.4.26
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 override {
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 // Regression test for bug fix in 1.2.9.
938 DEFINE_TESTCASE(emptydb1, backend) {
939  Xapian::Database db(get_database(string()));
940  static const Xapian::Query::op ops[] = {
952  };
953  for (Xapian::Query::op op : ops) {
954  tout << op << '\n';
955  Xapian::Enquire enquire(db);
957  enquire.set_query(query);
958  Xapian::MSet mset = enquire.get_mset(0, 10);
962  }
963 }
964 
971 DEFINE_TESTCASE(multiargop1, backend) {
972  Xapian::Database db(get_database("apitest_simpledata"));
973  static const struct { unsigned hits; Xapian::Query::op op; } tests[] = {
974  { 0, Xapian::Query::OP_AND },
975  { 6, Xapian::Query::OP_OR },
977  { 5, Xapian::Query::OP_XOR },
980  { 0, Xapian::Query::OP_NEAR },
984  { 6, Xapian::Query::OP_MAX }
985  };
986  static const char* terms[] = {"two", "all", "paragraph", "banana"};
987  Xapian::Enquire enquire(db);
988  for (auto& test : tests) {
989  Xapian::Query::op op = test.op;
990  Xapian::doccount hits = test.hits;
991  tout << op << " should give " << hits << " hits\n";
992  Xapian::Query query(op, terms, terms + 4);
993  enquire.set_query(query);
994  Xapian::MSet mset = enquire.get_mset(0, 10);
995  TEST_EQUAL(mset.get_matches_estimated(), hits);
996  TEST_EQUAL(mset.get_matches_upper_bound(), hits);
997  TEST_EQUAL(mset.get_matches_lower_bound(), hits);
998  }
999 }
1000 
1002 // Regression test for bug fixed in 1.3.1 and 1.2.11.
1003 DEFINE_TESTCASE(stubdb7, !backend) {
1005  Xapian::Database("nosuchdirectory", Xapian::DB_BACKEND_STUB));
1007  Xapian::WritableDatabase("nosuchdirectory",
1009 }
1010 
1012 // This runs for multi_* too, so serves to check that we get the same weights
1013 // with multiple databases as without.
1014 DEFINE_TESTCASE(msetweights1, backend) {
1015  Xapian::Database db = get_database("apitest_simpledata");
1016  Xapian::Enquire enq(db);
1018  Xapian::Query("paragraph"),
1019  Xapian::Query("word"));
1020  enq.set_query(q);
1021  // 5 documents match, and the 4th and 5th have the same weight, so ask for
1022  // 4 as that's a good test that we get the right one in this case.
1023  Xapian::MSet mset = enq.get_mset(0, 4);
1024 
1025  static const struct { Xapian::docid did; double wt; } expected[] = {
1026  { 2, 1.2058248004573934864 },
1027  { 4, 0.81127876655507624726 },
1028  { 1, 0.17309550762546158098 },
1029  { 3, 0.14609528172558261527 }
1030  };
1031 
1032  TEST_EQUAL(mset.size(), sizeof(expected) / sizeof(expected[0]));
1033  for (Xapian::doccount i = 0; i < mset.size(); ++i) {
1034  TEST_EQUAL(*mset[i], expected[i].did);
1035  TEST_EQUAL_DOUBLE(mset[i].get_weight(), expected[i].wt);
1036  }
1037 
1038  // Now test a query which matches only even docids, so in the multi case
1039  // one subdatabase doesn't match.
1040  enq.set_query(Xapian::Query("one"));
1041  mset = enq.get_mset(0, 3);
1042 
1043  static const struct { Xapian::docid did; double wt; } expected2[] = {
1044  { 6, 0.73354729848273669823 },
1045  { 2, 0.45626501034348893038 }
1046  };
1047 
1048  TEST_EQUAL(mset.size(), sizeof(expected2) / sizeof(expected2[0]));
1049  for (Xapian::doccount i = 0; i < mset.size(); ++i) {
1050  TEST_EQUAL(*mset[i], expected2[i].did);
1051  TEST_EQUAL_DOUBLE(mset[i].get_weight(), expected2[i].wt);
1052  }
1053 }
1054 
1055 DEFINE_TESTCASE(itorskiptofromend1, backend) {
1056  Xapian::Database db = get_database("apitest_simpledata");
1057 
1059  t.skip_to("zzzzz");
1060  TEST(t == db.termlist_end(1));
1061  // This worked in 1.2.x but segfaulted in 1.3.1.
1062  t.skip_to("zzzzzz");
1063 
1065  p.skip_to(99999);
1066  TEST(p == db.postlist_end("one"));
1067  // This segfaulted prior to 1.3.2.
1068  p.skip_to(999999);
1069 
1071  i.skip_to(99999);
1072  TEST(i == db.positionlist_end(6, "one"));
1073  // This segfaulted prior to 1.3.2.
1074  i.skip_to(999999);
1075 
1077  v.skip_to(99999);
1078  TEST(v == db.valuestream_end(1));
1079  // These segfaulted prior to 1.3.2.
1080  v.skip_to(999999);
1081  v.check(9999999);
1082 }
1083 
1085 // Regression test for bug fixed in 1.2.17 and 1.3.2 - the size gets fixed
1086 // but the uncorrected size was passed to the base file. Also, abort() was
1087 // called on 0.
1088 DEFINE_TESTCASE(blocksize1, chert || glass) {
1089  string db_dir = "." + get_dbtype();
1090  mkdir(db_dir.c_str(), 0755);
1091  db_dir += "/db__blocksize1";
1092  int flags;
1093  if (get_dbtype() == "chert") {
1095  } else {
1097  }
1098  static const unsigned bad_sizes[] = {
1099  65537, 8000, 2000, 1024, 16, 7, 3, 1, 0
1100  };
1101  for (size_t i = 0; i < sizeof(bad_sizes) / sizeof(bad_sizes[0]); ++i) {
1102  size_t block_size = bad_sizes[i];
1103  rm_rf(db_dir);
1104  Xapian::WritableDatabase db(db_dir, flags, block_size);
1105  Xapian::Document doc;
1106  doc.add_term("XYZ");
1107  doc.set_data("foo");
1108  db.add_document(doc);
1109  db.commit();
1110  }
1111 }
1112 
1114 DEFINE_TESTCASE(notermlist1, glass) {
1115  string db_dir = "." + get_dbtype();
1116  mkdir(db_dir.c_str(), 0755);
1117  db_dir += "/db__notermlist1";
1119  if (get_dbtype() == "chert") {
1120  flags |= Xapian::DB_BACKEND_CHERT;
1121  } else {
1122  flags |= Xapian::DB_BACKEND_GLASS;
1123  }
1124  rm_rf(db_dir);
1125  Xapian::WritableDatabase db(db_dir, flags);
1126  Xapian::Document doc;
1127  doc.add_term("hello");
1128  doc.add_value(42, "answer");
1129  db.add_document(doc);
1130  db.commit();
1131  TEST(!file_exists(db_dir + "/termlist.glass"));
1133 }
1134 
1136 DEFINE_TESTCASE(newfreelistblock1, writable) {
1137  Xapian::Document doc;
1138  doc.add_term("foo");
1139  for (int i = 100; i < 120; ++i) {
1140  doc.add_term(str(i));
1141  }
1142 
1144  for (int j = 0; j < 50; ++j) {
1145  wdb.add_document(doc);
1146  }
1147  wdb.commit();
1148 
1149  for (int k = 0; k < 1000; ++k) {
1150  wdb.add_document(doc);
1151  wdb.commit();
1152  }
1153 }
1154 
1160 DEFINE_TESTCASE(readonlyparentdir1, chert || glass) {
1161 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
1162  string path = get_named_writable_database_path("readonlyparentdir1");
1163  // Fix permissions if the previous test was killed.
1164  (void)chmod(path.c_str(), 0700);
1165  mkdir(path.c_str(), 0777);
1166  mkdir((path + "/sub").c_str(), 0777);
1167  Xapian::WritableDatabase db = get_named_writable_database("readonlyparentdir1/sub");
1168  TEST(chmod(path.c_str(), 0500) == 0);
1169  try {
1170  Xapian::Document doc;
1171  doc.add_term("hello");
1172  doc.set_data("some text");
1173  db.add_document(doc);
1174  db.commit();
1175  } catch (...) {
1176  // Attempt to fix the permissions, otherwise things like "rm -rf" on
1177  // the source tree will fail.
1178  (void)chmod(path.c_str(), 0700);
1179  throw;
1180  }
1181  TEST(chmod(path.c_str(), 0700) == 0);
1182 #endif
1183 }
1184 
1185 static void
1187 {
1188  Xapian::Document doc;
1189  doc.add_posting("hurricane", 199881);
1190  doc.add_posting("hurricane", 203084);
1191  doc.add_posting("katrina", 199882);
1192  doc.add_posting("katrina", 202473);
1193  doc.add_posting("katrina", 203085);
1194  db.add_document(doc);
1195 }
1196 
1198 DEFINE_TESTCASE(phrasebug1, positional) {
1199  Xapian::Database db = get_database("phrasebug1", make_phrasebug1_db);
1200  static const char * const qterms[] = { "katrina", "hurricane" };
1201  Xapian::Enquire e(db);
1202  Xapian::Query q(Xapian::Query::OP_PHRASE, qterms, qterms + 2, 5);
1203  e.set_query(q);
1204  Xapian::MSet mset = e.get_mset(0, 100);
1205  TEST_EQUAL(mset.size(), 0);
1206  static const char * const qterms2[] = { "hurricane", "katrina" };
1207  Xapian::Query q2(Xapian::Query::OP_PHRASE, qterms2, qterms2 + 2, 5);
1208  e.set_query(q2);
1209  mset = e.get_mset(0, 100);
1210  TEST_EQUAL(mset.size(), 1);
1211 }
1212 
1214 DEFINE_TESTCASE(retrylock1, writable && path) {
1215  // FIXME: Can't see an easy way to test this for remote databases - the
1216  // harness doesn't seem to provide a suitable way to reopen a remote.
1217 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR
1218  int fds[2];
1219  if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, PF_UNSPEC, fds) < 0) {
1220  FAIL_TEST("socketpair() failed");
1221  }
1222  if (fds[1] >= FD_SETSIZE)
1223  SKIP_TEST("socketpair() gave fd >= FD_SETSIZE");
1224  if (fcntl(fds[1], F_SETFL, O_NONBLOCK) < 0)
1225  FAIL_TEST("fcntl() failed to set O_NONBLOCK");
1226  pid_t child = fork();
1227  if (child == -1)
1228  FAIL_TEST("fork() failed");
1229  if (child == 0) {
1230  // Wait for signal that parent has opened the database.
1231  char ch;
1232  while (read(fds[0], &ch, 1) < 0) { }
1233 
1234  try {
1237  if (write(fds[0], "y", 1)) { }
1238  } catch (const Xapian::DatabaseLockError &) {
1239  if (write(fds[0], "l", 1)) { }
1240  } catch (const Xapian::Error &e) {
1241  const string & m = e.get_description();
1242  if (write(fds[0], m.data(), m.size())) { }
1243  } catch (...) {
1244  if (write(fds[0], "o", 1)) { }
1245  }
1246  _exit(0);
1247  }
1248 
1249  close(fds[0]);
1250 
1252  if (write(fds[1], "", 1) != 1)
1253  FAIL_TEST("Failed to signal to child process");
1254 
1255  char result[256];
1256  int r = read(fds[1], result, sizeof(result));
1257  if (r == -1) {
1258  if (errno == EAGAIN) {
1259  // Good.
1260  result[0] = 'y';
1261  } else {
1262  // Error.
1263  tout << "errno=" << errno << ": " << errno_to_string(errno) << '\n';
1264  result[0] = 'e';
1265  }
1266  r = 1;
1267  } else if (r >= 1) {
1268  if (result[0] == 'y') {
1269  // Child process managed to also get write lock!
1270  result[0] = '!';
1271  }
1272  } else {
1273  // EOF.
1274  result[0] = 'z';
1275  r = 1;
1276  }
1277 
1278  try {
1279  db.close();
1280  } catch (...) {
1281  kill(child, SIGKILL);
1282  int status;
1283  while (waitpid(child, &status, 0) < 0) {
1284  if (errno != EINTR) break;
1285  }
1286  throw;
1287  }
1288 
1289  if (result[0] == 'y') {
1290 retry:
1291  struct timeval tv;
1292  tv.tv_sec = 3;
1293  tv.tv_usec = 0;
1294  fd_set fdset;
1295  FD_ZERO(&fdset);
1296  FD_SET(fds[1], &fdset);
1297  int sr = select(fds[1] + 1, &fdset, NULL, NULL, &tv);
1298  if (sr == 0) {
1299  // Timed out.
1300  result[0] = 'T';
1301  r = 1;
1302  } else if (sr == -1) {
1303  if (errno == EINTR || errno == EAGAIN)
1304  goto retry;
1305  tout << "select() failed with errno=" << errno << ": "
1306  << errno_to_string(errno) << '\n';
1307  result[0] = 'S';
1308  r = 1;
1309  } else {
1310  r = read(fds[1], result, sizeof(result));
1311  if (r == -1) {
1312  // Error.
1313  tout << "read() failed with errno=" << errno << ": "
1314  << errno_to_string(errno) << '\n';
1315  result[0] = 'R';
1316  r = 1;
1317  } else if (r == 0) {
1318  // EOF.
1319  result[0] = 'Z';
1320  r = 1;
1321  }
1322  }
1323  }
1324 
1325  close(fds[1]);
1326 
1327  kill(child, SIGKILL);
1328  int status;
1329  while (waitpid(child, &status, 0) < 0) {
1330  if (errno != EINTR) break;
1331  }
1332 
1333  tout << string(result, r) << '\n';
1334  TEST_EQUAL(result[0], 'y');
1335 #endif
1336 }
1337 
1338 // Opening a WritableDatabase with low fds available - it should avoid them.
1339 DEFINE_TESTCASE(dbfilefd012, writable && !remote) {
1340 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
1341  int oldfds[3];
1342  for (int i = 0; i < 3; ++i) {
1343  oldfds[i] = dup(i);
1344  }
1345  try {
1346  for (int j = 0; j < 3; ++j) {
1347  close(j);
1348  TEST_REL(lseek(j, 0, SEEK_CUR), <, 0);
1349  TEST_EQUAL(errno, EBADF);
1350  }
1351 
1353 
1354  // Check we didn't use any of those low fds for tables, as that risks
1355  // data corruption if some other code in the same process tries to
1356  // write to them (see #651).
1357  for (int fd = 0; fd < 3; ++fd) {
1358  // Check that the fd is still closed, or isn't open O_RDWR (the
1359  // lock file gets opened O_WRONLY), or it's a pipe (if we're using
1360  // a child process to hold a non-OFD fcntl lock).
1361  int flags = fcntl(fd, F_GETFL);
1362  if (flags == -1) {
1363  TEST_EQUAL(errno, EBADF);
1364  } else if ((flags & O_ACCMODE) != O_RDWR) {
1365  // OK.
1366  } else {
1367  struct stat sb;
1368  TEST_NOT_EQUAL(fstat(fd, &sb), -1);
1369 #ifdef S_ISSOCK
1370  TEST(S_ISSOCK(sb.st_mode));
1371 #else
1372  // If we can't check it is a socket, at least check it is not a
1373  // regular file.
1374  TEST(!S_ISREG(sb.st_mode));
1375 #endif
1376  }
1377  }
1378  } catch (...) {
1379  for (int j = 0; j < 3; ++j) {
1380  dup2(oldfds[j], j);
1381  close(oldfds[j]);
1382  }
1383  throw;
1384  }
1385 
1386  for (int j = 0; j < 3; ++j) {
1387  dup2(oldfds[j], j);
1388  close(oldfds[j]);
1389  }
1390 #endif
1391 }
1392 
1394 DEFINE_TESTCASE(cursorbug1, writable && path) {
1397  Xapian::Enquire enq(db);
1399  Xapian::MSet mset;
1400  // The original problem triggers for chert and glass on repeat==7.
1401  for (int repeat = 0; repeat < 10; ++repeat) {
1402  tout.str(string());
1403  tout << "iteration #" << repeat << '\n';
1404 
1405  const int ITEMS = 10;
1406  int free_id = db.get_doccount();
1407  int offset = max(free_id, ITEMS * 2) - (ITEMS * 2);
1408  int limit = offset + (ITEMS * 2);
1409 
1410  mset = enq.get_mset(offset, limit);
1411  for (Xapian::MSetIterator m1 = mset.begin(); m1 != mset.end(); ++m1) {
1412  (void)m1.get_document().get_value(0);
1413  }
1414 
1415  for (int i = free_id; i <= free_id + ITEMS; ++i) {
1416  Xapian::Document doc;
1417  const string & id = str(i);
1418  string qterm = "Q" + id;
1419  doc.add_value(0, id);
1420  doc.add_boolean_term(qterm);
1421  wdb.replace_document(qterm, doc);
1422  }
1423  wdb.commit();
1424 
1425  db.reopen();
1426  mset = enq.get_mset(offset, limit);
1427  for (Xapian::MSetIterator m2 = mset.begin(); m2 != mset.end(); ++m2) {
1428  (void)m2.get_document().get_value(0);
1429  }
1430  }
1431 }
1432 
1433 // Regression test for #674, fixed in 1.2.21 and 1.3.3.
1434 DEFINE_TESTCASE(sortvalue2, backend) {
1435  Xapian::Database db = get_database("apitest_simpledata");
1436  db.add_database(get_database("apitest_simpledata2"));
1437  Xapian::Enquire enq(db);
1439  enq.set_sort_by_value(0, false);
1440  Xapian::MSet mset = enq.get_mset(0, 50);
1441 
1442  // Check all results are in key order - the bug was that they were sorted
1443  // by docid instead with multiple remote databases.
1444  string old_key;
1445  for (Xapian::MSetIterator i = mset.begin(); i != mset.end(); ++i) {
1446  string key = db.get_document(*i).get_value(0);
1447  TEST(old_key <= key);
1448  swap(old_key, key);
1449  }
1450 }
1451 
1453 DEFINE_TESTCASE(enquiregetquery1, backend) {
1454  Xapian::Database db = get_database("apitest_simpledata");
1455  Xapian::Enquire enq(db);
1456  TEST_EQUAL(enq.get_query().get_description(), "Query()");
1457 }
1458 
1459 DEFINE_TESTCASE(embedded1, singlefile) {
1460  // In reality you should align the embedded database to a multiple of
1461  // database block size, but any offset is meant to work.
1462  off_t offset = 1234;
1463 
1464  Xapian::Database db = get_database("apitest_simpledata");
1465  const string & db_path = get_database_path("apitest_simpledata");
1466  const string & tmp_path = db_path + "-embedded";
1467  ofstream out(tmp_path, fstream::trunc|fstream::binary);
1468  out.seekp(offset);
1469  out << ifstream(db_path, fstream::binary).rdbuf();
1470  out.close();
1471 
1472  {
1473  int fd = open(tmp_path.c_str(), O_RDONLY|O_BINARY);
1474  lseek(fd, offset, SEEK_SET);
1475  Xapian::Database db_embedded(fd);
1476  TEST_EQUAL(db.get_doccount(), db_embedded.get_doccount());
1477  }
1478 
1479  {
1480  int fd = open(tmp_path.c_str(), O_RDONLY|O_BINARY);
1481  lseek(fd, offset, SEEK_SET);
1482  size_t check_errors =
1484  TEST_EQUAL(check_errors, 0);
1485  }
1486 }
1487 
1489 DEFINE_TESTCASE(exactxor1, backend) {
1490  Xapian::Database db = get_database("apitest_simpledata");
1491  Xapian::Enquire enq(db);
1492 
1493  static const char * const words[4] = {
1494  "blank", "test", "paragraph", "banana"
1495  };
1496  Xapian::Query q(Xapian::Query::OP_XOR, words, words + 4);
1497  enq.set_query(q);
1499  Xapian::MSet mset = enq.get_mset(0, 0);
1500  // A reversed conditional gave us 5 in this case.
1502  // Test improved lower bound in 1.3.7 (earlier versions gave 0).
1504 
1505  static const char * const words2[4] = {
1506  "queri", "test", "paragraph", "word"
1507  };
1508  Xapian::Query q2(Xapian::Query::OP_XOR, words2, words2 + 4);
1509  enq.set_query(q2);
1511  mset = enq.get_mset(0, 0);
1512  // A reversed conditional gave us 6 in this case.
1514  // Test improved lower bound in 1.3.7 (earlier versions gave 0).
1516 }
1517 
1519 DEFINE_TESTCASE(getrevision1, chert || glass) {
1521  TEST_EQUAL(db.get_revision(), 0);
1522  db.commit();
1523  TEST_EQUAL(db.get_revision(), 0);
1524  Xapian::Document doc;
1525  doc.add_term("hello");
1526  db.add_document(doc);
1527  TEST_EQUAL(db.get_revision(), 0);
1528  db.commit();
1529  TEST_EQUAL(db.get_revision(), 1);
1530  db.commit();
1531  TEST_EQUAL(db.get_revision(), 1);
1532  db.add_document(doc);
1533  db.commit();
1534  TEST_EQUAL(db.get_revision(), 2);
1535 }
1536 
1538 DEFINE_TESTCASE(getrevision2, !backend) {
1539  Xapian::Database db;
1540  TEST_EQUAL(db.get_revision(), 0);
1541  Xapian::Database wdb;
1542  TEST_EQUAL(wdb.get_revision(), 0);
1543 }
1544 
1546 DEFINE_TESTCASE(getdocumentlazy1, backend) {
1547  Xapian::Database db = get_database("apitest_simpledata");
1549  Xapian::Document doc = db.get_document(2);
1550  TEST_EQUAL(doc.get_data(), doc_lazy.get_data());
1551  TEST_EQUAL(doc.get_value(0), doc_lazy.get_value(0));
1552 }
1553 
1555 DEFINE_TESTCASE(getdocumentlazy2, backend) {
1556  Xapian::Database db = get_database("apitest_simpledata");
1557  Xapian::Document doc;
1558  try {
1560  } catch (const Xapian::DocNotFoundError&) {
1561  // DOC_ASSUME_VALID is really just a hint, so ignoring is OK (the
1562  // remote backend currently does).
1563  }
1564  TEST(doc.get_data().empty());
1566  doc = db.get_document(db.get_lastdocid() + 1);
1567  );
1568 }
1569 
1570 static void
1572 {
1573  Xapian::Document doc;
1574  doc.add_term("foo");
1575  doc.add_boolean_term("bar");
1576  db.add_document(doc);
1577  Xapian::Document doc2;
1578  doc2.add_posting("foo", 0, 2);
1579  doc2.add_term("foo2");
1580  doc2.add_boolean_term("baz");
1581  doc2.add_boolean_term("baz2");
1582  db.add_document(doc2);
1583 }
1584 
1585 DEFINE_TESTCASE(getuniqueterms1, backend) {
1586  Xapian::Database db =
1587  get_database("uniqterms_gt_doclen", gen_uniqterms_gt_doclen_db);
1588 
1589  auto unique1 = db.get_unique_terms(1);
1590  TEST_REL(unique1, <=, db.get_doclength(1));
1591  TEST_REL(unique1, <, db.get_document(1).termlist_count());
1592  // Ideally it'd be equal to 1, and in this case it is, but the current
1593  // backends can't always efficiently ensure an exact answer.
1594  TEST_REL(unique1, >=, 1);
1595 
1596  auto unique2 = db.get_unique_terms(2);
1597  TEST_REL(unique2, <=, db.get_doclength(2));
1598  TEST_REL(unique2, <, db.get_document(2).termlist_count());
1599  // Ideally it'd be equal to 2, but the current backends can't always
1600  // efficiently ensure an exact answer and here it is actually 3.
1601  TEST_REL(unique2, >=, 2);
1602 }
1603 
1610 DEFINE_TESTCASE(nopositionbug1, backend) {
1611  Xapian::Database db =
1612  get_database("uniqterms_gt_doclen", gen_uniqterms_gt_doclen_db);
1613 
1614  // Test both orders.
1615  static const char* const terms1[] = { "foo", "baz" };
1616  static const char* const terms2[] = { "baz", "foo" };
1617 
1618  Xapian::Enquire enq(db);
1620  begin(terms1), end(terms1), 10));
1621  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1622 
1624  begin(terms2), end(terms2), 10));
1625  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1626 
1628  begin(terms1), end(terms1), 10));
1629  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1630 
1632  begin(terms2), end(terms2), 10));
1633  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1634 
1635  // Exercise exact phrase case too.
1637  begin(terms1), end(terms1), 2));
1638  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1639 
1641  begin(terms2), end(terms2), 2));
1642  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1643 }
1644 
1649 DEFINE_TESTCASE(checkatleast4, backend) {
1650  Xapian::Database db = get_database("apitest_simpledata");
1651  Xapian::Enquire enq(db);
1652  enq.set_query(Xapian::Query("paragraph"));
1653  // This used to cause access to an element in an empty vector.
1654  Xapian::MSet mset = enq.get_mset(0, 0, 4);
1655  TEST_EQUAL(mset.size(), 0);
1656 }
1657 
1659 DEFINE_TESTCASE(freelistleak1, check) {
1660  auto path = get_database_path("freelistleak1",
1661  [](Xapian::WritableDatabase& wdb,
1662  const string&)
1663  {
1664  wdb.set_metadata("foo", "bar");
1665  wdb.commit();
1666  Xapian::Document doc;
1667  doc.add_term("baz");
1668  wdb.add_document(doc);
1669  });
1670  size_t check_errors =
1672  TEST_EQUAL(check_errors, 0);
1673 }
1674 
1676 DEFINE_TESTCASE(splitpostings1, writable) {
1678  Xapian::Document doc;
1679  // Add postings to create a split internally.
1680  for (Xapian::termpos pos = 0; pos <= 100; pos += 10) {
1681  doc.add_posting("foo", pos);
1682  }
1683  for (Xapian::termpos pos = 5; pos <= 100; pos += 20) {
1684  doc.add_posting("foo", pos);
1685  }
1686  db.add_document(doc);
1687  db.commit();
1688 
1689  Xapian::termpos expect = 0;
1690  Xapian::termpos pos = 0;
1691  for (auto p = db.positionlist_begin(1, "foo");
1692  p != db.positionlist_end(1, "foo"); ++p) {
1693  TEST_REL(expect, <=, 100);
1694  pos = *p;
1695  TEST_EQUAL(pos, expect);
1696  expect += 5;
1697  if (expect % 20 == 15) expect += 5;
1698  }
1699  TEST_EQUAL(pos, 100);
1700 }
1701 
1703 DEFINE_TESTCASE(multidb1, backend) {
1704  Xapian::Database db;
1705  TEST_EQUAL(db.size(), 0);
1706  Xapian::Database db2 = get_database("apitest_simpledata");
1707  TEST(db2.size() != 0);
1708  db.add_database(db2);
1709  TEST_EQUAL(db.size(), db2.size());
1710  db.add_database(db2);
1711  // Regression test for bug introduced and fixed in git master before 1.5.0.
1712  // Adding a multi database to an empty database incorrectly worked just
1713  // like assigning the database object. The list of shards is now copied
1714  // instead.
1715  TEST_EQUAL(db.size(), db2.size() * 2);
1717  TEST_EQUAL(db.size(), db2.size() * 2);
1718 }
1719 
1720 // Test that all the terms returned exist.
1721 DEFINE_TESTCASE(allterms7, backend) {
1722  Xapian::Database db = get_database("etext");
1723  for (auto i = db.allterms_begin(); i != db.allterms_end(); ++i) {
1724  string term = *i;
1725  TEST(db.get_termfreq(term) > 0);
1726  TEST(db.postlist_begin(term) != db.postlist_end(term));
1727  }
1728 }
1729 
1730 /* Test searching for non-existent terms returns zero results.
1731  *
1732  * Regression test for GlassTable::readahead_key() throwing "Key too long"
1733  * error if passed an oversized key.
1734  */
1735 DEFINE_TESTCASE(nosuchterm, backend) {
1736  Xapian::Database db = get_database("apitest_simpledata");
1737  Xapian::Enquire enquire{db};
1738  // Test up to a length longer than any backend supports.
1739  const unsigned MAX_LEN = 300;
1740  string term;
1741  term.reserve(MAX_LEN);
1742  while (term.size() < MAX_LEN) {
1743  term += 'x';
1744  enquire.set_query(Xapian::Query(term));
1745  TEST_EQUAL(enquire.get_mset(0, 10).size(), 0);
1746  }
1747 }
1748 
1749 // Test exception for check() on remote via stub.
1750 DEFINE_TESTCASE(unsupportedcheck1, path) {
1751  mkdir(".stub", 0755);
1752  const char* stubpath = ".stub/unsupportedcheck1";
1753  ofstream out(stubpath);
1754  TEST(out.is_open());
1755  out << "remote :" << BackendManager::get_xapian_progsrv_command()
1756  << ' ' << get_database_path("apitest_simpledata") << '\n';
1757  out.close();
1758 
1760  Xapian::Database::check(stubpath));
1761 }
1762 
1763 // Test exception for check() on inmemory via stub.
1764 DEFINE_TESTCASE(unsupportedcheck2, inmemory) {
1765  mkdir(".stub", 0755);
1766  const char* stubpath = ".stub/unsupportedcheck2";
1767  ofstream out(stubpath);
1768  TEST(out.is_open());
1769  out << "inmemory\n";
1770  out.close();
1771 
1773  Xapian::Database::check(stubpath));
1774 }
1775 
1776 // Test exception for passing empty filename to check().
1777 DEFINE_TESTCASE(unsupportedcheck3, !backend) {
1778  // Regression test, exception was DatabaseOpeningError with description:
1779  // Failed to rewind file descriptor -1 (Bad file descriptor)
1780  try {
1781  Xapian::Database::check(string());
1782  } catch (const Xapian::DatabaseOpeningError& e) {
1783  string enoent_msg = errno_to_string(ENOENT);
1784  TEST_EQUAL(e.get_error_string(), enoent_msg);
1785  }
1786 }
1787 
1788 // Test handling of corrupt DB with out of range values in the version file.
1789 // Regression test for #824, fixed in 1.4.25.
1790 DEFINE_TESTCASE(corruptglass1, glass) {
1791  string db_path =
1792  test_driver::get_srcdir() + "/testdata/glass_corrupt_db1";
1793 
1794  for (int n = '1'; n <= '3'; ++n) {
1795  db_path.back() = n;
1797  Xapian::Database db(db_path));
1798 
1800  Xapian::Database::check(db_path));
1801  }
1802 }
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:875
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:938
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:129
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:433
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:104
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.
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::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:162
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:142
#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
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:848
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
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
bool operator()(const Xapian::Document &) const override
Decide whether we want this document to be in the MSet.
Definition: api_backend.cc:423
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