xapian-core  2.0.0
api_backend.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2008-2023 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, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 
24 #include "api_backend.h"
25 
26 #include <xapian.h>
27 
28 #include "backendmanager.h"
29 #include "errno_to_string.h"
30 #include "filetests.h"
31 #include "net/resolver.h"
32 #include "str.h"
33 #include "socket_utils.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 "safenetdb.h"
43 #include "safesysstat.h"
44 #include "safeunistd.h"
45 #ifdef HAVE_SOCKETPAIR
46 # include "safesyssocket.h"
47 # include <signal.h>
48 # include "safesyswait.h"
49 #endif
50 
51 #include <cerrno>
52 #include <fstream>
53 #include <iterator>
54 
55 using namespace std;
56 
58 DEFINE_TESTCASE(lockfileumask1, glass) {
59 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
60  mode_t old_umask = umask(022);
61  try {
63 
64  string path = get_named_writable_database_path("lockfileumask1");
65  path += "/flintlock";
66 
67  struct stat statbuf;
68  TEST(stat(path.c_str(), &statbuf) == 0);
69  TEST_EQUAL(statbuf.st_mode & 0777, 0644);
70  } catch (...) {
71  umask(old_umask);
72  throw;
73  }
74 
75  umask(old_umask);
76 #endif
77 }
78 
80 DEFINE_TESTCASE(totaldoclen1, writable) {
82  Xapian::Document doc;
83  doc.add_posting("foo", 1, 2000000000);
84  db.add_document(doc);
85  Xapian::Document doc2;
86  doc2.add_posting("bar", 1, 2000000000);
87  db.add_document(doc2);
88  TEST_EQUAL(db.get_avlength(), 2000000000);
89  TEST_EQUAL(db.get_total_length(), 4000000000ull);
90  db.commit();
91  TEST_EQUAL(db.get_avlength(), 2000000000);
92  TEST_EQUAL(db.get_total_length(), 4000000000ull);
93  for (int i = 0; i != 20; ++i) {
94  Xapian::Document doc3;
95  doc3.add_posting("count" + str(i), 1, 2000000000);
96  db.add_document(doc3);
97  }
98  TEST_EQUAL(db.get_avlength(), 2000000000);
99  TEST_EQUAL(db.get_total_length(), 44000000000ull);
100  db.commit();
101  TEST_EQUAL(db.get_avlength(), 2000000000);
102  TEST_EQUAL(db.get_total_length(), 44000000000ull);
103  if (get_dbtype() != "inmemory") {
104  // InMemory doesn't support get_writable_database_as_database().
106  TEST_EQUAL(dbr.get_avlength(), 2000000000);
107  TEST_EQUAL(dbr.get_total_length(), 44000000000ull);
108  }
109 }
110 
111 // Check that exceeding 32bit in combined database doesn't cause a problem
112 // when using 64bit docids.
113 DEFINE_TESTCASE(exceed32bitcombineddb1, writable) {
114  // Test case is for 64-bit Xapian::docid.
115  // FIXME: Though we should check that the overflow is handled gracefully
116  // for 32-bit...
117  if constexpr(sizeof(Xapian::docid) == 4) {
118  SKIP_TEST("Not supported with 32-bit docid currently");
119  } else {
120  // The InMemory backend uses a vector for the documents, so trying to
121  // add a document with the maximum docid is likely to fail because we
122  // can't allocate enough memory!
123  SKIP_TEST_FOR_BACKEND("inmemory");
124 
126  Xapian::Document doc;
127  doc.set_data("prose");
128  doc.add_term("word");
129  Xapian::docid max_32bit_id = 0xffffffff;
130  db1.replace_document(max_32bit_id, doc);
131  db1.commit();
132 
134 
135  Xapian::Database db;
136  db.add_database(db1);
137  db.add_database(db2);
138 
139  Xapian::Enquire enquire(db);
141  Xapian::MSet mymset = enquire.get_mset(0, 10);
142 
143  TEST_EQUAL(2, mymset.size());
144 
145  // We can't usefully check the shard docid if the testharness backend
146  // is multi.
147  bool multi = (db1.size() > 1);
148  for (Xapian::MSetIterator i = mymset.begin(); i != mymset.end(); ++i) {
149  doc = i.get_document();
150  if (!multi)
151  TEST_EQUAL(doc.get_docid(), max_32bit_id);
152  TEST_EQUAL(doc.get_data(), "prose");
153  }
154  }
155 }
156 
157 DEFINE_TESTCASE(dbstats1, backend) {
158  Xapian::Database db = get_database("etext");
159 
160  // Use precalculated values to avoid expending CPU cycles to calculate
161  // these every time without improving test coverage.
162  const Xapian::termcount min_len = 2;
163  const Xapian::termcount max_len = 532;
164  const Xapian::termcount max_wdf = 22;
165 
166  if (get_dbtype() != "inmemory") {
167  // Should be exact as no deletions have happened.
168  TEST_EQUAL(db.get_doclength_upper_bound(), max_len);
169  TEST_EQUAL(db.get_doclength_lower_bound(), min_len);
170  } else {
171  // For inmemory, we usually give rather loose bounds.
172  TEST_REL(db.get_doclength_upper_bound(),>=,max_len);
173  TEST_REL(db.get_doclength_lower_bound(),<=,min_len);
174  }
175 
176  if (get_dbtype() != "inmemory" &&
177  get_dbtype().find("remote") == string::npos) {
178  TEST_EQUAL(db.get_wdf_upper_bound("the"), max_wdf);
179  } else {
180  // For inmemory and remote backends, we usually give rather loose
181  // bounds (remote matches use tighter bounds, but querying the
182  // wdf bound gives a looser one).
183  TEST_REL(db.get_wdf_upper_bound("the"),>=,max_wdf);
184  }
185 
186  // This failed with an assertion during development between 1.3.1 and
187  // 1.3.2.
188  TEST_EQUAL(db.get_wdf_upper_bound(""), 0);
189 
190  if (get_dbtype() == "honey") {
191  const Xapian::termcount min_unique_len = 2;
192  const Xapian::termcount max_unique_len = 272;
193  TEST_EQUAL(db.get_unique_terms_lower_bound(), min_unique_len);
194  TEST_EQUAL(db.get_unique_terms_upper_bound(), max_unique_len);
195  }
196 }
197 
198 // Check stats with a single document. In a multi-database situation, this
199 // gave 0 for get-_doclength_lower_bound() in 1.3.2.
200 DEFINE_TESTCASE(dbstats2, backend) {
201  Xapian::Database db = get_database("apitest_onedoc");
202 
203  // Use precalculated values to avoid expending CPU cycles to calculate
204  // these every time without improving test coverage.
205  const Xapian::termcount min_len = 15;
206  const Xapian::termcount max_len = 15;
207  const Xapian::termcount max_wdf = 7;
208 
209  if (get_dbtype() != "inmemory") {
210  // Should be exact as no deletions have happened.
211  TEST_EQUAL(db.get_doclength_upper_bound(), max_len);
212  TEST_EQUAL(db.get_doclength_lower_bound(), min_len);
213  } else {
214  // For inmemory, we usually give rather loose bounds.
215  TEST_REL(db.get_doclength_upper_bound(),>=,max_len);
216  TEST_REL(db.get_doclength_lower_bound(),<=,min_len);
217  }
218 
219  if (get_dbtype() != "inmemory" &&
220  get_dbtype().find("remote") == string::npos) {
221  TEST_EQUAL(db.get_wdf_upper_bound("word"), max_wdf);
222  } else {
223  // For inmemory and remote backends, we usually give rather loose
224  // bounds (remote matches use tighter bounds, but querying the
225  // wdf bound gives a looser one).
226  TEST_REL(db.get_wdf_upper_bound("word"),>=,max_wdf);
227  }
228 
229  TEST_EQUAL(db.get_wdf_upper_bound(""), 0);
230 
231  if (get_dbtype() == "honey") {
232  const Xapian::termcount min_unique_len = 9;
233  const Xapian::termcount max_unique_len = 9;
234  TEST_EQUAL(db.get_unique_terms_lower_bound(), min_unique_len);
235  TEST_EQUAL(db.get_unique_terms_upper_bound(), max_unique_len);
236  }
237 }
238 
240 DEFINE_TESTCASE(alldocspl3, backend) {
241  Xapian::Database db = get_database(string());
242 
243  TEST_EQUAL(db.get_termfreq(string()), 0);
244  TEST_EQUAL(db.get_collection_freq(string()), 0);
245  TEST(db.postlist_begin(string()) == db.postlist_end(string()));
246 
247  TEST_EQUAL(db.get_termfreq(string_view()), 0);
248  TEST_EQUAL(db.get_collection_freq(string_view()), 0);
249  TEST(db.postlist_begin(string_view()) == db.postlist_end(string_view()));
250 }
251 
253 DEFINE_TESTCASE(modifiedpostlist1, writable) {
255  Xapian::Document a, b;
256  Xapian::Enquire enq(db);
257 
258  a.add_term("T");
259  enq.set_query(Xapian::Query("T"));
260 
261  db.replace_document(2, a);
262  db.commit();
263  db.replace_document(1, a);
264  db.replace_document(1, b);
265 
266  mset_expect_order(enq.get_mset(0, 2), 2);
267 }
268 
270 DEFINE_TESTCASE(doclenaftercommit1, writable) {
275  db.commit();
276  TEST_EQUAL(db.get_doclength(1), 0);
277  TEST_EQUAL(db.get_unique_terms(1), 0);
278 }
279 
280 DEFINE_TESTCASE(valuesaftercommit1, writable) {
282  Xapian::Document doc;
283  doc.add_value(0, "value");
284  db.replace_document(2, doc);
285  db.commit();
286  db.replace_document(1, doc);
287  db.replace_document(3, doc);
288  TEST_EQUAL(db.get_document(3).get_value(0), "value");
289  db.commit();
290  TEST_EQUAL(db.get_document(3).get_value(0), "value");
291 }
292 
293 DEFINE_TESTCASE(lockfilefd0or1, glass) {
294 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
295  int old_stdin = dup(0);
296  int old_stdout = dup(1);
297  try {
298  // With fd 0 available.
299  close(0);
300  {
304  }
305  // With fd 0 and fd 1 available.
306  close(1);
307  {
311  }
312  // With fd 1 available.
313  dup2(old_stdin, 0);
314  {
318  }
319  } catch (...) {
320  dup2(old_stdin, 0);
321  dup2(old_stdout, 1);
322  close(old_stdin);
323  close(old_stdout);
324  throw;
325  }
326 
327  dup2(old_stdout, 1);
328  close(old_stdin);
329  close(old_stdout);
330 #endif
331 }
332 
334 DEFINE_TESTCASE(lockfilealreadyopen1, glass) {
335  // Ensure database has been created.
336  (void)get_named_writable_database("lockfilealreadyopen1");
337  string path = get_named_writable_database_path("lockfilealreadyopen1");
338  int fd = ::open((path + "/flintlock").c_str(), O_RDONLY);
339  TEST(fd != -1);
340  try {
344  );
345  } catch (...) {
346  close(fd);
347  throw;
348  }
349  close(fd);
350 }
351 
353 DEFINE_TESTCASE(testlock1, glass) {
354  Xapian::Database rdb;
355  TEST(!rdb.locked());
356  {
358  TEST(db.locked());
359  Xapian::Database db_as_database = db;
360  TEST(db_as_database.locked());
361  TEST(!rdb.locked());
363  TEST(db.locked());
364  TEST(db_as_database.locked());
365  try {
366  TEST(rdb.locked());
367  } catch (const Xapian::FeatureUnavailableError&) {
368  SKIP_TEST("Database::locked() not supported on this platform");
369  }
370  db_as_database = rdb;
371  TEST(db.locked());
372  TEST(db_as_database.locked());
373  TEST(rdb.locked());
374  db_as_database.close();
375  TEST(db.locked());
376  TEST(rdb.locked());
377  // After close(), locked() should either work as if close() hadn't been
378  // called or throw Xapian::DatabaseClosedError.
379  try {
380  TEST(db_as_database.locked());
381  } catch (const Xapian::DatabaseClosedError&) {
382  }
383  db.close();
384  TEST(!rdb.locked());
385  try {
386  TEST(!db_as_database.locked());
387  } catch (const Xapian::DatabaseClosedError&) {
388  }
389  }
390  TEST(!rdb.locked());
391 }
392 
397 DEFINE_TESTCASE(testlock2, backend && !writable) {
398  Xapian::Database db = get_database("apitest_simpledata");
399  TEST(!db.locked());
400  db.close();
401  TEST(!db.locked());
402 }
403 
416 DEFINE_TESTCASE(testlock3, inmemory) {
417  Xapian::Database db = get_database("apitest_simpledata");
418  TEST(db.locked());
419  db.close();
420  TEST(!db.locked());
421 }
422 
424 DEFINE_TESTCASE(testlock4, glass) {
425  Xapian::Database rdb;
426  TEST(!rdb.locked());
427 
428  {
430  TEST(db.locked());
431  Xapian::Database db_as_database = db;
432  TEST(db_as_database.locked());
433  TEST(!rdb.locked());
434 
435  {
437  // Test lock() fails (already open to write as db).
439  auto wdb = rdb.lock());
440  }
441 
442  rdb = db.unlock();
443  try {
444  TEST(!rdb.locked());
445  // unlock() should have closed the underlying WritableDatabase so
446  // locked() should either report that it isn't locked, or throw
447  // Xapian::DatabaseClosedError.
448  try {
449  TEST(!db.locked());
450  } catch (const Xapian::DatabaseClosedError&) {
451  }
452  try {
453  TEST(!db_as_database.locked());
454  } catch (const Xapian::DatabaseClosedError&) {
455  }
456  } catch (const Xapian::FeatureUnavailableError&) {
457  SKIP_TEST("Database::locked() not supported on this platform");
458  }
459 
460  db.close();
461  TEST(!db.locked());
462  TEST(!db_as_database.locked());
463  TEST(!rdb.locked());
465  db.lock());
467  db.unlock());
469  db_as_database.lock());
471  db_as_database.unlock());
472 
473  {
474  auto wdb = rdb.lock();
475  TEST(rdb.locked());
476  }
477 
478  rdb.close();
480  rdb.lock());
482  rdb.unlock());
483  }
484 }
485 
487  mutable bool called;
488 
489  public:
490  CheckMatchDecider() : called(false) { }
491 
492  bool operator()(const Xapian::Document &) const override {
493  called = true;
494  return true;
495  }
496 
497  bool was_called() const { return called; }
498 };
499 
501 DEFINE_TESTCASE(matchdecider4, remote) {
502  Xapian::Database db(get_database("apitest_simpledata"));
503  Xapian::Enquire enquire(db);
504  enquire.set_query(Xapian::Query("paragraph"));
505 
506  CheckMatchDecider mdecider;
507  Xapian::MSet mset;
508 
510  mset = enquire.get_mset(0, 10, NULL, &mdecider));
511  TEST(!mdecider.was_called());
512 }
513 
517 DEFINE_TESTCASE(replacedoc7, writable && !inmemory && !remote) {
518  // The inmemory backend doesn't batch changes, so there's nothing to
519  // check there.
520  //
521  // The remote backend doesn't implement the lazy replacement of documents
522  // optimisation currently.
524  Xapian::Document doc;
525  doc.set_data("fish");
526  doc.add_term("Hlocalhost");
527  doc.add_posting("hello", 1);
528  doc.add_posting("world", 2);
529  doc.add_value(1, "myvalue");
530  db.add_document(doc);
531  db.commit();
532 
533  // We add a second document, and then replace the first document with
534  // itself 10000 times. If the document count for the database reopened
535  // read-only is 2, then we triggered an automatic commit.
536 
537  doc.add_term("XREV2");
538  db.add_document(doc);
539 
540  for (int i = 0; i < 10000; ++i) {
541  doc = db.get_document(1);
542  db.replace_document(1, doc);
543  }
544 
546  TEST_EQUAL(rodb.get_doccount(), 1);
547 
548  db.commit();
549  TEST(rodb.reopen());
550 
551  TEST_EQUAL(rodb.get_doccount(), 2);
552 }
553 
558 DEFINE_TESTCASE(replacedoc8, writable) {
560  {
561  Xapian::Document doc;
562  doc.set_data("fish");
563  doc.add_term("takeaway");
564  db.add_document(doc);
565  }
566  db.delete_document(1);
567  {
568  Xapian::Document doc;
569  doc.set_data("chips");
570  doc.add_term("takeaway", 2);
571  db.replace_document(1, doc);
572  }
573  db.commit();
574  TEST_EQUAL(db.get_collection_freq("takeaway"), 2);
575  Xapian::PostingIterator p = db.postlist_begin("takeaway");
576  TEST(p != db.postlist_end("takeaway"));
577  TEST_EQUAL(p.get_wdf(), 2);
578 }
579 
584 DEFINE_TESTCASE(replacedoc9, writable) {
586  {
587  Xapian::Document doc;
588  doc.set_data("food");
589  doc.add_posting("falafel", 1);
590  db.add_document(doc);
591  }
592  db.commit();
593  Xapian::Document doc = db.get_document(1);
594  doc.clear_terms();
595  doc.add_term("falafel");
596  db.replace_document(1, doc);
597  db.commit();
598 
599  // The positions should have been removed, but the bug meant they weren't.
600  TEST_EQUAL(db.positionlist_begin(1, "falafel"),
601  db.positionlist_end(1, "falafel"));
602 }
603 
605 DEFINE_TESTCASE(databasemodified1, writable && !inmemory && !multi) {
606  // The inmemory backend doesn't support revisions.
607  //
608  // With multi, DatabaseModifiedError doesn't trigger as easily.
610  Xapian::Document doc;
611  doc.set_data("cargo");
612  doc.add_term("abc");
613  doc.add_term("def");
614  doc.add_term("ghi");
615  const int N = 500;
616  for (int i = 0; i < N; ++i) {
617  db.add_document(doc);
618  }
619  db.commit();
620 
622  db.add_document(doc);
623  db.commit();
624 
625  db.add_document(doc);
626  db.commit();
627 
628  db.add_document(doc);
629  try {
630  TEST_EQUAL(*rodb.termlist_begin(N - 1), "abc");
631  FAIL_TEST("Expected DatabaseModifiedError wasn't thrown");
632  } catch (const Xapian::DatabaseModifiedError &) {
633  }
634 
635  try {
636  Xapian::Enquire enq(rodb);
637  enq.set_query(Xapian::Query("abc"));
638  Xapian::MSet mset = enq.get_mset(0, 10);
639  FAIL_TEST("Expected DatabaseModifiedError wasn't thrown");
640  } catch (const Xapian::DatabaseModifiedError &) {
641  }
642 }
643 
645 DEFINE_TESTCASE(qpmemoryleak1, writable && !inmemory) {
646  // Inmemory never throws DatabaseModifiedError.
648  Xapian::Document doc;
649 
650  doc.add_term("foo");
651  for (int i = 100; i < 120; ++i) {
652  doc.add_term(str(i));
653  }
654 
655  for (int j = 0; j < 50; ++j) {
656  wdb.add_document(doc);
657  }
658  wdb.commit();
659 
661  Xapian::QueryParser queryparser;
662  queryparser.set_database(database);
664  for (int k = 0; k < 1000; ++k) {
665  wdb.add_document(doc);
666  wdb.commit();
667  (void)queryparser.parse_query("1", queryparser.FLAG_PARTIAL);
668  },
669  SKIP_TEST("didn't manage to trigger DatabaseModifiedError");
670  );
671 }
672 
673 static void
675 {
676  const char * value0 =
677  "ABBCDEFGHIJKLMMNOPQQRSTTUUVVWXYZZaabcdefghhijjkllmnopqrsttuvwxyz";
678  const char * value1 =
679  "EMLEMMMMMMMNMMLMELEDNLEDMLMLDMLMLMLMEDGFHPOPBAHJIQJNGRKCGF";
680  while (*value0) {
681  Xapian::Document doc;
682  doc.add_value(0, string(1, *value0++));
683  if (*value1) {
684  doc.add_value(1, string(1, *value1++));
685  doc.add_term("K1");
686  }
687  db.add_document(doc);
688  }
689 }
690 
692 DEFINE_TESTCASE(msize1, backend) {
694  Xapian::Enquire enq(db);
695  enq.set_sort_by_value(1, false);
696  enq.set_collapse_key(0);
697  enq.set_query(Xapian::Query("K1"));
698 
699  Xapian::MSet mset = enq.get_mset(0, 60);
703  TEST_EQUAL(lb, ub);
704  TEST_EQUAL(lb, est);
705 
706  Xapian::MSet mset2 = enq.get_mset(50, 10, 1000);
710  TEST_EQUAL(lb2, ub2);
711  TEST_EQUAL(lb2, est2);
712  TEST_EQUAL(est, est2);
713 
714  Xapian::MSet mset3 = enq.get_mset(0, 10, 1000);
718  TEST_EQUAL(lb3, ub3);
719  TEST_EQUAL(lb3, est3);
720  TEST_EQUAL(est, est3);
721 }
722 
723 static void
725 {
726  const char * value0 = "AAABCDEEFGHIIJJKLLMNNOOPPQQRSTTUVWXYZ";
727  const char * value1 = "MLEMNMLMLMEDEDEMLEMLMLMLPOAHGF";
728  while (*value0) {
729  Xapian::Document doc;
730  doc.add_value(0, string(1, *value0++));
731  if (*value1) {
732  doc.add_value(1, string(1, *value1++));
733  doc.add_term("K1");
734  }
735  db.add_document(doc);
736  }
737 }
738 
740 DEFINE_TESTCASE(msize2, backend) {
742  Xapian::Enquire enq(db);
743  enq.set_sort_by_value(1, false);
744  enq.set_collapse_key(0);
745  enq.set_query(Xapian::Query("K1"));
746 
747  Xapian::MSet mset = enq.get_mset(0, 60);
751  TEST_EQUAL(lb, ub);
752  TEST_EQUAL(lb, est);
753 
754  Xapian::MSet mset2 = enq.get_mset(50, 10, 1000);
758  TEST_EQUAL(lb2, ub2);
759  TEST_EQUAL(lb2, est2);
760  TEST_EQUAL(est, est2);
761 
762  Xapian::MSet mset3 = enq.get_mset(0, 10, 1000);
766  TEST_EQUAL(lb3, ub3);
767  TEST_EQUAL(lb3, est3);
768  TEST_EQUAL(est, est3);
769 }
770 
771 static void
773 {
774  for (int n = 1; n != 50; ++n) {
775  Xapian::Document doc;
776  for (int i = 1; i != 50; ++i) {
777  if (n % i == 0)
778  doc.add_term("N" + str(i));
779  }
780  db.add_document(doc);
781  }
782 }
783 
785 DEFINE_TESTCASE(xordecay1, backend) {
787  Xapian::Enquire enq(db);
789  Xapian::Query("N10"),
791  Xapian::Query("N2"),
792  Xapian::Query("N3"))));
793  Xapian::MSet mset1 = enq.get_mset(0, 1);
794  Xapian::MSet msetall = enq.get_mset(0, db.get_doccount());
795 
796  TEST(mset_range_is_same(mset1, 0, msetall, 0, mset1.size()));
797 }
798 
799 static void
801 {
802  const char * p = "VJ=QC]LUNTaARLI;715RR^];A4O=P4ZG<2CS4EM^^VS[A6QENR";
803  for (int d = 0; p[d]; ++d) {
804  int l = int(p[d] - '0');
805  Xapian::Document doc;
806  for (int n = 1; n < l; ++n) {
807  doc.add_term("N" + str(n));
808  if (n % (d + 1) == 0) {
809  doc.add_term("M" + str(n));
810  }
811  }
812  db.add_document(doc);
813  }
814 }
815 
817 DEFINE_TESTCASE(ordecay1, backend) {
819  Xapian::Enquire enq(db);
821  Xapian::Query("N20"),
822  Xapian::Query("N21")));
823 
824  Xapian::MSet msetall = enq.get_mset(0, db.get_doccount());
825  for (unsigned int i = 1; i < msetall.size(); ++i) {
826  Xapian::MSet submset = enq.get_mset(0, i);
827  TEST(mset_range_is_same(submset, 0, msetall, 0, submset.size()));
828  }
829 }
830 
834 DEFINE_TESTCASE(ordecay2, backend) {
836  Xapian::Enquire enq(db);
837  std::vector<Xapian::Query> q;
838  q.push_back(Xapian::Query("M20"));
839  q.push_back(Xapian::Query("N21"));
840  q.push_back(Xapian::Query("N22"));
842  Xapian::Query("N25"),
844  q.begin(),
845  q.end())));
846 
847  Xapian::MSet msetall = enq.get_mset(0, db.get_doccount());
848  for (unsigned int i = 1; i < msetall.size(); ++i) {
849  Xapian::MSet submset = enq.get_mset(0, i);
850  TEST(mset_range_is_same(submset, 0, msetall, 0, submset.size()));
851  }
852 }
853 
854 static void
856 {
857  static const unsigned t1[] = {2, 4, 6, 8, 10};
858  static const unsigned t2[] = {6, 7, 8, 11, 12, 13, 14, 15, 16, 17};
859  static const unsigned t3[] = {3, 7, 8, 11, 12, 13, 14, 15, 16, 17};
860 
861  for (unsigned i = 1; i <= 17; ++i) {
862  Xapian::Document doc;
863  db.replace_document(i, doc);
864  }
865  for (unsigned i : t1) {
866  Xapian::Document doc(db.get_document(i));
867  doc.add_term("T1");
868  db.replace_document(i, doc);
869  }
870  for (unsigned i : t2) {
871  Xapian::Document doc(db.get_document(i));
872  doc.add_term("T2");
873  if (i < 17) {
874  doc.add_term("T2_lowfreq");
875  }
876  doc.add_value(2, "1");
877  db.replace_document(i, doc);
878  }
879  for (unsigned i : t3) {
880  Xapian::Document doc(db.get_document(i));
881  doc.add_term("T3");
882  if (i < 17) {
883  doc.add_term("T3_lowfreq");
884  }
885  doc.add_value(3, "1");
886  db.replace_document(i, doc);
887  }
888 }
889 
893 DEFINE_TESTCASE(orcheck1, backend) {
895  Xapian::Enquire enq(db);
896  Xapian::Query q1("T1");
897  Xapian::Query q2("T2");
898  Xapian::Query q2l("T2_lowfreq");
899  Xapian::Query q3("T3");
900  Xapian::Query q3l("T3_lowfreq");
903 
904  tout << "Checking q2 OR q3\n";
907  mset_expect_order(enq.get_mset(0, db.get_doccount()), 8, 6);
908 
909  tout << "Checking q2l OR q3\n";
912  mset_expect_order(enq.get_mset(0, db.get_doccount()), 8, 6);
913 
914  tout << "Checking q2 OR q3l\n";
917  mset_expect_order(enq.get_mset(0, db.get_doccount()), 8, 6);
918 
919  tout << "Checking v2 OR q3\n";
922  mset_expect_order(enq.get_mset(0, db.get_doccount()), 8, 6);
923 
924  tout << "Checking q2 OR v3\n";
927  // Order of results in this one is different, because v3 gives no weight,
928  // both documents are in q2, and document 8 has a higher length.
929  mset_expect_order(enq.get_mset(0, db.get_doccount()), 6, 8);
930 
931 }
932 
937 DEFINE_TESTCASE(failedreplace1, glass) {
939  Xapian::Document doc;
940  doc.add_term("foo");
941  db.add_document(doc);
942  Xapian::docid did = db.add_document(doc);
943  doc.add_term("abc");
944  doc.add_term(string(1000, 'm'));
945  doc.add_term("xyz");
947  db.commit();
948  TEST_EQUAL(db.get_doccount(), 0);
949  TEST_EQUAL(db.get_termfreq("foo"), 0);
950 }
951 
952 DEFINE_TESTCASE(failedreplace2, glass) {
953  Xapian::WritableDatabase db(get_writable_database("apitest_simpledata"));
954  db.commit();
955  Xapian::doccount db_size = db.get_doccount();
956  Xapian::Document doc;
957  doc.set_data("wibble");
958  doc.add_term("foo");
959  doc.add_value(0, "seven");
960  db.add_document(doc);
961  Xapian::docid did = db.add_document(doc);
962  doc.add_term("abc");
963  doc.add_term(string(1000, 'm'));
964  doc.add_term("xyz");
965  doc.add_value(0, "six");
967  db.commit();
968  TEST_EQUAL(db.get_doccount(), db_size);
969  TEST_EQUAL(db.get_termfreq("foo"), 0);
970 }
971 
973 DEFINE_TESTCASE(phrase3, positional) {
974  Xapian::Database db = get_database("apitest_phrase");
975 
976  static const char * const phrase_words[] = { "phrase", "near" };
977  Xapian::Query q(Xapian::Query::OP_NEAR, phrase_words, phrase_words + 2, 12);
979 
980  Xapian::Enquire enquire(db);
981  enquire.set_query(q);
982  Xapian::MSet mset = enquire.get_mset(0, 5);
983 
984 }
985 
987 // Regression test for fix in 1.2.4.
988 DEFINE_TESTCASE(msetfirst2, backend) {
989  Xapian::Database db(get_database("apitest_simpledata"));
990  Xapian::Enquire enquire(db);
991  enquire.set_query(Xapian::Query("paragraph"));
992  Xapian::MSet mset;
993  // Before the fix, this tried to allocate too much memory.
994  mset = enquire.get_mset(0xfffffff0, 1);
995  TEST_EQUAL(mset.get_firstitem(), 0xfffffff0);
996  // Check that the number of documents gets clamped too.
997  mset = enquire.get_mset(1, 0xfffffff0);
998  TEST_EQUAL(mset.get_firstitem(), 1);
999  // Another regression test - MatchNothing used to give an MSet with
1000  // get_firstitem() returning 0.
1002  mset = enquire.get_mset(1, 1);
1003  TEST_EQUAL(mset.get_firstitem(), 1);
1004 }
1005 
1006 // Regression test for bug fix in 1.2.9.
1007 DEFINE_TESTCASE(emptydb1, backend) {
1008  Xapian::Database db(get_database(string()));
1009  static const Xapian::Query::op ops[] = {
1021  };
1022  for (Xapian::Query::op op : ops) {
1023  tout << op << '\n';
1024  Xapian::Enquire enquire(db);
1026  enquire.set_query(query);
1027  Xapian::MSet mset = enquire.get_mset(0, 10);
1028  TEST_EQUAL(mset.get_matches_estimated(), 0);
1031  }
1032 }
1033 
1040 DEFINE_TESTCASE(multiargop1, backend) {
1041  Xapian::Database db(get_database("apitest_simpledata"));
1042  static const struct { unsigned hits; Xapian::Query::op op; } tests[] = {
1043  { 0, Xapian::Query::OP_AND },
1044  { 6, Xapian::Query::OP_OR },
1046  { 5, Xapian::Query::OP_XOR },
1048  { 0, Xapian::Query::OP_FILTER },
1049  { 0, Xapian::Query::OP_NEAR },
1050  { 0, Xapian::Query::OP_PHRASE },
1053  { 6, Xapian::Query::OP_MAX }
1054  };
1055  static const char* terms[] = {"two", "all", "paragraph", "banana"};
1056  Xapian::Enquire enquire(db);
1057  for (auto& test : tests) {
1058  Xapian::Query::op op = test.op;
1059  Xapian::doccount hits = test.hits;
1060  tout << op << " should give " << hits << " hits\n";
1061  Xapian::Query query(op, terms, terms + 4);
1062  enquire.set_query(query);
1063  Xapian::MSet mset = enquire.get_mset(0, 10);
1064  TEST_EQUAL(mset.get_matches_estimated(), hits);
1065  TEST_EQUAL(mset.get_matches_upper_bound(), hits);
1066  TEST_EQUAL(mset.get_matches_lower_bound(), hits);
1067  }
1068 }
1069 
1071 // Regression test for bug fixed in 1.3.1 and 1.2.11.
1072 DEFINE_TESTCASE(stubdb7, !backend) {
1074  Xapian::Database("nosuchdirectory", Xapian::DB_BACKEND_STUB));
1076  Xapian::WritableDatabase("nosuchdirectory",
1078 }
1079 
1081 // This runs for multi_* too, so serves to check that we get the same weights
1082 // with multiple databases as without.
1083 DEFINE_TESTCASE(msetweights1, backend) {
1084  Xapian::Database db = get_database("apitest_simpledata");
1085  Xapian::Enquire enq(db);
1087  Xapian::Query("paragraph"),
1088  Xapian::Query("word"));
1089  enq.set_query(q);
1090  // 5 documents match, and the 4th and 5th have the same weight, so ask for
1091  // 4 as that's a good test that we get the right one in this case.
1092  Xapian::MSet mset = enq.get_mset(0, 4);
1093 
1094  static const struct { Xapian::docid did; double wt; } expected[] = {
1095  { 2, 1.2058248004573934864 },
1096  { 4, 0.81127876655507624726 },
1097  { 1, 0.17309550762546158098 },
1098  { 3, 0.14609528172558261527 }
1099  };
1100 
1101  TEST_EQUAL(mset.size(), sizeof(expected) / sizeof(expected[0]));
1102  for (Xapian::doccount i = 0; i < mset.size(); ++i) {
1103  TEST_EQUAL(*mset[i], expected[i].did);
1104  TEST_EQUAL_DOUBLE(mset[i].get_weight(), expected[i].wt);
1105  }
1106 
1107  // Now test a query which matches only even docids, so in the multi case
1108  // one subdatabase doesn't match.
1109  enq.set_query(Xapian::Query("one"));
1110  mset = enq.get_mset(0, 3);
1111 
1112  static const struct { Xapian::docid did; double wt; } expected2[] = {
1113  { 6, 0.73354729848273669823 },
1114  { 2, 0.45626501034348893038 }
1115  };
1116 
1117  TEST_EQUAL(mset.size(), sizeof(expected2) / sizeof(expected2[0]));
1118  for (Xapian::doccount i = 0; i < mset.size(); ++i) {
1119  TEST_EQUAL(*mset[i], expected2[i].did);
1120  TEST_EQUAL_DOUBLE(mset[i].get_weight(), expected2[i].wt);
1121  }
1122 }
1123 
1124 DEFINE_TESTCASE(itorskiptofromend1, backend) {
1125  Xapian::Database db = get_database("apitest_simpledata");
1126 
1128  t.skip_to("zzzzz");
1129  TEST(t == db.termlist_end(1));
1130  // This worked in 1.2.x but segfaulted in 1.3.1.
1131  t.skip_to("zzzzzz");
1132 
1134  p.skip_to(99999);
1135  TEST(p == db.postlist_end("one"));
1136  // This segfaulted prior to 1.3.2.
1137  p.skip_to(999999);
1138 
1140  i.skip_to(99999);
1141  TEST(i == db.positionlist_end(6, "one"));
1142  // This segfaulted prior to 1.3.2.
1143  i.skip_to(999999);
1144 
1146  v.skip_to(99999);
1147  TEST(v == db.valuestream_end(1));
1148  // These segfaulted prior to 1.3.2.
1149  v.skip_to(999999);
1150  v.check(9999999);
1151 }
1152 
1154 // Regression test for bug fixed in 1.2.17 and 1.3.2 - the size gets fixed
1155 // but the uncorrected size was passed to the base file. Also, abort() was
1156 // called on 0.
1157 DEFINE_TESTCASE(blocksize1, glass) {
1158  string db_dir = "." + get_dbtype();
1159  mkdir(db_dir.c_str(), 0755);
1160  db_dir += "/db__blocksize1";
1161  int flags;
1162  if (get_dbtype() == "glass") {
1164  } else {
1165  FAIL_TEST("Unhandled backend type");
1166  }
1167  static const int bad_sizes[] = {
1168  65537, 8000, 2000, 1024, 16, 7, 3, 1, 0
1169  };
1170  for (int block_size : bad_sizes) {
1171  rm_rf(db_dir);
1172  Xapian::WritableDatabase db(db_dir, flags, block_size);
1173  Xapian::Document doc;
1174  doc.add_term("XYZ");
1175  doc.set_data("foo");
1176  db.add_document(doc);
1177  db.commit();
1178  }
1179 }
1180 
1182 DEFINE_TESTCASE(notermlist1, glass) {
1183  string db_dir = "." + get_dbtype();
1184  mkdir(db_dir.c_str(), 0755);
1185  db_dir += "/db__notermlist1";
1187  if (get_dbtype() == "glass") {
1188  flags |= Xapian::DB_BACKEND_GLASS;
1189  }
1190  rm_rf(db_dir);
1191  Xapian::WritableDatabase db(db_dir, flags);
1192  Xapian::Document doc;
1193  doc.add_term("hello");
1194  doc.add_value(42, "answer");
1195  db.add_document(doc);
1196  db.commit();
1197  TEST(!file_exists(db_dir + "/termlist.glass"));
1199 }
1200 
1202 DEFINE_TESTCASE(newfreelistblock1, writable) {
1203  Xapian::Document doc;
1204  doc.add_term("foo");
1205  for (int i = 100; i < 120; ++i) {
1206  doc.add_term(str(i));
1207  }
1208 
1210  for (int j = 0; j < 50; ++j) {
1211  wdb.add_document(doc);
1212  }
1213  wdb.commit();
1214 
1215  for (int k = 0; k < 1000; ++k) {
1216  wdb.add_document(doc);
1217  wdb.commit();
1218  }
1219 }
1220 
1226 DEFINE_TESTCASE(readonlyparentdir1, glass) {
1227 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
1228  string path = get_named_writable_database_path("readonlyparentdir1");
1229  // Fix permissions if the previous test was killed.
1230  (void)chmod(path.c_str(), 0700);
1231  mkdir(path.c_str(), 0777);
1232  mkdir((path + "/sub").c_str(), 0777);
1233  Xapian::WritableDatabase db = get_named_writable_database("readonlyparentdir1/sub");
1234  TEST(chmod(path.c_str(), 0500) == 0);
1235  try {
1236  Xapian::Document doc;
1237  doc.add_term("hello");
1238  doc.set_data("some text");
1239  db.add_document(doc);
1240  db.commit();
1241  } catch (...) {
1242  // Attempt to fix the permissions, otherwise things like "rm -rf" on
1243  // the source tree will fail.
1244  (void)chmod(path.c_str(), 0700);
1245  throw;
1246  }
1247  TEST(chmod(path.c_str(), 0700) == 0);
1248 #endif
1249 }
1250 
1251 static void
1253 {
1254  Xapian::Document doc;
1255  doc.add_posting("hurricane", 199881);
1256  doc.add_posting("hurricane", 203084);
1257  doc.add_posting("katrina", 199882);
1258  doc.add_posting("katrina", 202473);
1259  doc.add_posting("katrina", 203085);
1260  db.add_document(doc);
1261 }
1262 
1264 DEFINE_TESTCASE(phrasebug1, positional) {
1265  Xapian::Database db = get_database("phrasebug1", make_phrasebug1_db);
1266  static const char * const qterms[] = { "katrina", "hurricane" };
1267  Xapian::Enquire e(db);
1268  Xapian::Query q(Xapian::Query::OP_PHRASE, qterms, qterms + 2, 5);
1269  e.set_query(q);
1270  Xapian::MSet mset = e.get_mset(0, 100);
1271  TEST_EQUAL(mset.size(), 0);
1272  static const char * const qterms2[] = { "hurricane", "katrina" };
1273  Xapian::Query q2(Xapian::Query::OP_PHRASE, qterms2, qterms2 + 2, 5);
1274  e.set_query(q2);
1275  mset = e.get_mset(0, 100);
1276  TEST_EQUAL(mset.size(), 1);
1277 }
1278 
1280 DEFINE_TESTCASE(retrylock1, writable && path) {
1281  // FIXME: Can't see an easy way to test this for remote databases - the
1282  // harness doesn't seem to provide a suitable way to reopen a remote.
1283 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR
1284  int fds[2];
1285  if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, PF_UNSPEC, fds) < 0) {
1286  FAIL_TEST("socketpair() failed");
1287  }
1288  if (fds[1] >= FD_SETSIZE)
1289  SKIP_TEST("socketpair() gave fd >= FD_SETSIZE");
1290  if (fcntl(fds[1], F_SETFL, O_NONBLOCK) < 0)
1291  FAIL_TEST("fcntl() failed to set O_NONBLOCK");
1292  pid_t child = fork();
1293  if (child == -1)
1294  FAIL_TEST("fork() failed");
1295  if (child == 0) {
1296  // Wait for signal that parent has opened the database.
1297  char ch;
1298  while (read(fds[0], &ch, 1) < 0) { }
1299 
1300  try {
1303  if (write(fds[0], "y", 1)) { }
1304  } catch (const Xapian::DatabaseLockError &) {
1305  if (write(fds[0], "l", 1)) { }
1306  } catch (const Xapian::Error &e) {
1307  const string & m = e.get_description();
1308  if (write(fds[0], m.data(), m.size())) { }
1309  } catch (...) {
1310  if (write(fds[0], "o", 1)) { }
1311  }
1312  _exit(0);
1313  }
1314 
1315  close(fds[0]);
1316 
1318  if (write(fds[1], "", 1) != 1)
1319  FAIL_TEST("Failed to signal to child process");
1320 
1321  char result[256];
1322  int r = read(fds[1], result, sizeof(result));
1323  if (r == -1) {
1324  if (errno == EAGAIN) {
1325  // Good.
1326  result[0] = 'y';
1327  } else {
1328  // Error.
1329  tout << "errno=" << errno << ": " << errno_to_string(errno) << '\n';
1330  result[0] = 'e';
1331  }
1332  r = 1;
1333  } else if (r >= 1) {
1334  if (result[0] == 'y') {
1335  // Child process managed to also get write lock!
1336  result[0] = '!';
1337  }
1338  } else {
1339  // EOF.
1340  result[0] = 'z';
1341  r = 1;
1342  }
1343 
1344  try {
1345  db.close();
1346  } catch (...) {
1347  kill(child, SIGKILL);
1348  int status;
1349  while (waitpid(child, &status, 0) < 0) {
1350  if (errno != EINTR) break;
1351  }
1352  throw;
1353  }
1354 
1355  if (result[0] == 'y') {
1356 retry:
1357  struct timeval tv;
1358  tv.tv_sec = 3;
1359  tv.tv_usec = 0;
1360  fd_set fdset;
1361  FD_ZERO(&fdset);
1362  FD_SET(fds[1], &fdset);
1363  int sr = select(fds[1] + 1, &fdset, NULL, NULL, &tv);
1364  if (sr == 0) {
1365  // Timed out.
1366  result[0] = 'T';
1367  r = 1;
1368  } else if (sr == -1) {
1369  if (errno == EINTR || errno == EAGAIN)
1370  goto retry;
1371  tout << "select() failed with errno=" << errno << ": "
1372  << errno_to_string(errno) << '\n';
1373  result[0] = 'S';
1374  r = 1;
1375  } else {
1376  r = read(fds[1], result, sizeof(result));
1377  if (r == -1) {
1378  // Error.
1379  tout << "read() failed with errno=" << errno << ": "
1380  << errno_to_string(errno) << '\n';
1381  result[0] = 'R';
1382  r = 1;
1383  } else if (r == 0) {
1384  // EOF.
1385  result[0] = 'Z';
1386  r = 1;
1387  }
1388  }
1389  }
1390 
1391  close(fds[1]);
1392 
1393  kill(child, SIGKILL);
1394  int status;
1395  while (waitpid(child, &status, 0) < 0) {
1396  if (errno != EINTR) break;
1397  }
1398 
1399  tout << string(result, r) << '\n';
1400  TEST_EQUAL(result[0], 'y');
1401 #endif
1402 }
1403 
1404 // Opening a WritableDatabase with low fds available - it should avoid them.
1405 DEFINE_TESTCASE(dbfilefd012, writable && !remote) {
1406 #if !defined __WIN32__ && !defined __CYGWIN__ && !defined __OS2__
1407  int oldfds[3];
1408  for (int i = 0; i < 3; ++i) {
1409  oldfds[i] = dup(i);
1410  }
1411  try {
1412  for (int j = 0; j < 3; ++j) {
1413  close(j);
1414  TEST_REL(lseek(j, 0, SEEK_CUR), <, 0);
1415  TEST_EQUAL(errno, EBADF);
1416  }
1417 
1419 
1420  // Check we didn't use any of those low fds for tables, as that risks
1421  // data corruption if some other code in the same process tries to
1422  // write to them (see #651).
1423  for (int fd = 0; fd < 3; ++fd) {
1424  // Check that the fd is still closed, or isn't open O_RDWR (the
1425  // lock file gets opened O_WRONLY), or it's a pipe (if we're using
1426  // a child process to hold a non-OFD fcntl lock).
1427  int flags = fcntl(fd, F_GETFL);
1428  if (flags == -1) {
1429  TEST_EQUAL(errno, EBADF);
1430  } else if ((flags & O_ACCMODE) != O_RDWR) {
1431  // OK.
1432  } else {
1433  struct stat sb;
1434  TEST_NOT_EQUAL(fstat(fd, &sb), -1);
1435 #ifdef S_ISSOCK
1436  TEST(S_ISSOCK(sb.st_mode));
1437 #else
1438  // If we can't check it is a socket, at least check it is not a
1439  // regular file.
1440  TEST(!S_ISREG(sb.st_mode));
1441 #endif
1442  }
1443  }
1444  } catch (...) {
1445  for (int j = 0; j < 3; ++j) {
1446  dup2(oldfds[j], j);
1447  close(oldfds[j]);
1448  }
1449  throw;
1450  }
1451 
1452  for (int j = 0; j < 3; ++j) {
1453  dup2(oldfds[j], j);
1454  close(oldfds[j]);
1455  }
1456 #endif
1457 }
1458 
1460 DEFINE_TESTCASE(cursorbug1, writable && path) {
1463  Xapian::Enquire enq(db);
1465  Xapian::MSet mset;
1466  // The original problem triggered for chert and glass on repeat==7.
1467  for (int repeat = 0; repeat < 10; ++repeat) {
1468  tout.str(string());
1469  tout << "iteration #" << repeat << '\n';
1470 
1471  const int ITEMS = 10;
1472  int free_id = db.get_doccount();
1473  int offset = max(free_id, ITEMS * 2) - (ITEMS * 2);
1474  int limit = offset + (ITEMS * 2);
1475 
1476  mset = enq.get_mset(offset, limit);
1477  for (Xapian::MSetIterator m1 = mset.begin(); m1 != mset.end(); ++m1) {
1478  (void)m1.get_document().get_value(0);
1479  }
1480 
1481  for (int i = free_id; i <= free_id + ITEMS; ++i) {
1482  Xapian::Document doc;
1483  const string & id = str(i);
1484  string qterm = "Q" + id;
1485  doc.add_value(0, id);
1486  doc.add_boolean_term(qterm);
1487  wdb.replace_document(qterm, doc);
1488  }
1489  wdb.commit();
1490 
1491  db.reopen();
1492  mset = enq.get_mset(offset, limit);
1493  for (Xapian::MSetIterator m2 = mset.begin(); m2 != mset.end(); ++m2) {
1494  (void)m2.get_document().get_value(0);
1495  }
1496  }
1497 }
1498 
1499 // Regression test for #674, fixed in 1.2.21 and 1.3.3.
1500 DEFINE_TESTCASE(sortvalue2, backend) {
1501  Xapian::Database db = get_database("apitest_simpledata");
1502  db.add_database(get_database("apitest_simpledata2"));
1503  Xapian::Enquire enq(db);
1505  enq.set_sort_by_value(0, false);
1506  Xapian::MSet mset = enq.get_mset(0, 50);
1507 
1508  // Check all results are in key order - the bug was that they were sorted
1509  // by docid instead with multiple remote databases.
1510  string old_key;
1511  for (Xapian::MSetIterator i = mset.begin(); i != mset.end(); ++i) {
1512  string key = db.get_document(*i).get_value(0);
1513  TEST(old_key <= key);
1514  swap(old_key, key);
1515  }
1516 }
1517 
1519 DEFINE_TESTCASE(enquiregetquery1, backend) {
1520  Xapian::Database db = get_database("apitest_simpledata");
1521  Xapian::Enquire enq(db);
1522  TEST_EQUAL(enq.get_query().get_description(), "Query()");
1523 }
1524 
1525 DEFINE_TESTCASE(embedded1, singlefile) {
1526  // In reality you should align the embedded database to a multiple of
1527  // database block size, but any offset is meant to work.
1528  off_t offset = 1234;
1529 
1530  Xapian::Database db = get_database("apitest_simpledata");
1531  const string & db_path = get_database_path("apitest_simpledata");
1532  const string & tmp_path = db_path + "-embedded";
1533  ofstream out(tmp_path, fstream::trunc|fstream::binary);
1534  out.seekp(offset);
1535  out << ifstream(db_path, fstream::binary).rdbuf();
1536  out.close();
1537 
1538  {
1539  int fd = open(tmp_path.c_str(), O_RDONLY|O_BINARY);
1540  lseek(fd, offset, SEEK_SET);
1541  Xapian::Database db_embedded(fd);
1542  TEST_EQUAL(db.get_doccount(), db_embedded.get_doccount());
1543  }
1544 
1545  {
1546  int fd = open(tmp_path.c_str(), O_RDONLY|O_BINARY);
1547  lseek(fd, offset, SEEK_SET);
1548  size_t check_errors =
1550  TEST_EQUAL(check_errors, 0);
1551  }
1552 }
1553 
1555 DEFINE_TESTCASE(exactxor1, backend) {
1556  Xapian::Database db = get_database("apitest_simpledata");
1557  Xapian::Enquire enq(db);
1558 
1559  static const char * const words[4] = {
1560  "blank", "test", "paragraph", "banana"
1561  };
1562  Xapian::Query q(Xapian::Query::OP_XOR, words, words + 4);
1563  enq.set_query(q);
1565  Xapian::MSet mset = enq.get_mset(0, 0);
1566  // A reversed conditional gave us 5 in this case.
1568  // Test improved lower bound in 1.3.7 (earlier versions gave 0).
1570 
1571  static const char * const words2[4] = {
1572  "queri", "test", "paragraph", "word"
1573  };
1574  Xapian::Query q2(Xapian::Query::OP_XOR, words2, words2 + 4);
1575  enq.set_query(q2);
1577  mset = enq.get_mset(0, 0);
1578  // A reversed conditional gave us 6 in this case.
1580  // Test improved lower bound in 1.3.7 (earlier versions gave 0).
1582 }
1583 
1585 DEFINE_TESTCASE(getrevision1, glass) {
1587  TEST_EQUAL(db.get_revision(), 0);
1588  db.commit();
1589  TEST_EQUAL(db.get_revision(), 0);
1590  Xapian::Document doc;
1591  doc.add_term("hello");
1592  db.add_document(doc);
1593  TEST_EQUAL(db.get_revision(), 0);
1594  db.commit();
1595  TEST_EQUAL(db.get_revision(), 1);
1596  db.commit();
1597  TEST_EQUAL(db.get_revision(), 1);
1598  db.add_document(doc);
1599  db.commit();
1600  TEST_EQUAL(db.get_revision(), 2);
1601 }
1602 
1604 DEFINE_TESTCASE(getrevision2, !backend) {
1605  Xapian::Database db;
1606  TEST_EQUAL(db.get_revision(), 0);
1607  Xapian::Database wdb;
1608  TEST_EQUAL(wdb.get_revision(), 0);
1609 }
1610 
1612 DEFINE_TESTCASE(getdocumentlazy1, backend) {
1613  Xapian::Database db = get_database("apitest_simpledata");
1615  Xapian::Document doc = db.get_document(2);
1616  TEST_EQUAL(doc.get_data(), doc_lazy.get_data());
1617  TEST_EQUAL(doc.get_value(0), doc_lazy.get_value(0));
1618 }
1619 
1621 DEFINE_TESTCASE(getdocumentlazy2, backend) {
1622  Xapian::Database db = get_database("apitest_simpledata");
1623  Xapian::Document doc;
1624  try {
1626  } catch (const Xapian::DocNotFoundError&) {
1627  // DOC_ASSUME_VALID is really just a hint, so ignoring is OK (the
1628  // remote backend currently does).
1629  }
1630  TEST(doc.get_data().empty());
1632  doc = db.get_document(db.get_lastdocid() + 1);
1633  );
1634 }
1635 
1636 static void
1638 {
1639  Xapian::Document doc;
1640  doc.add_term("foo");
1641  doc.add_boolean_term("bar");
1642  db.add_document(doc);
1643  Xapian::Document doc2;
1644  doc2.add_posting("foo", 0, 2);
1645  doc2.add_term("foo2");
1646  doc2.add_boolean_term("baz");
1647  doc2.add_boolean_term("baz2");
1648  db.add_document(doc2);
1649 }
1650 
1651 DEFINE_TESTCASE(getuniqueterms1, backend) {
1652  Xapian::Database db =
1653  get_database("uniqterms_gt_doclen", gen_uniqterms_gt_doclen_db);
1654 
1655  auto unique1 = db.get_unique_terms(1);
1656  TEST_REL(unique1, <=, db.get_doclength(1));
1657  TEST_REL(unique1, <, db.get_document(1).termlist_count());
1658  // Ideally it'd be equal to 1, and in this case it is, but the current
1659  // backends can't always efficiently ensure an exact answer.
1660  TEST_REL(unique1, >=, 1);
1661 
1662  auto unique2 = db.get_unique_terms(2);
1663  TEST_REL(unique2, <=, db.get_doclength(2));
1664  TEST_REL(unique2, <, db.get_document(2).termlist_count());
1665  // Ideally it'd be equal to 2, but the current backends can't always
1666  // efficiently ensure an exact answer and here it is actually 3.
1667  TEST_REL(unique2, >=, 2);
1668 }
1669 
1676 DEFINE_TESTCASE(nopositionbug1, backend) {
1677  Xapian::Database db =
1678  get_database("uniqterms_gt_doclen", gen_uniqterms_gt_doclen_db);
1679 
1680  // Test both orders.
1681  static const char* const terms1[] = { "foo", "baz" };
1682  static const char* const terms2[] = { "baz", "foo" };
1683 
1684  Xapian::Enquire enq(db);
1686  begin(terms1), end(terms1), 10));
1687  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1688 
1690  begin(terms2), end(terms2), 10));
1691  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1692 
1694  begin(terms1), end(terms1), 10));
1695  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1696 
1698  begin(terms2), end(terms2), 10));
1699  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1700 
1701  // Exercise exact phrase case too.
1703  begin(terms1), end(terms1), 2));
1704  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1705 
1707  begin(terms2), end(terms2), 2));
1708  TEST_EQUAL(enq.get_mset(0, 5).size(), 0);
1709 }
1710 
1715 DEFINE_TESTCASE(termitertf1, backend) {
1716  Xapian::Database db = get_database("apitest_simpledata");
1718 
1719  t.skip_to("mset");
1720  TEST_EQUAL(*t, "mset");
1721  TEST_EQUAL(t.get_termfreq(), 1);
1722 
1723  t.skip_to("paragraph");
1724  TEST_EQUAL(*t, "paragraph");
1725  TEST_EQUAL(t.get_termfreq(), 5);
1726 
1727  t.skip_to("queri");
1728  TEST_EQUAL(*t, "queri");
1729  TEST_EQUAL(t.get_termfreq(), 3);
1730 }
1731 
1736 DEFINE_TESTCASE(checkatleast4, backend) {
1737  Xapian::Database db = get_database("apitest_simpledata");
1738  Xapian::Enquire enq(db);
1739  enq.set_query(Xapian::Query("paragraph"));
1740  // This used to cause access to an element in an empty vector.
1741  Xapian::MSet mset = enq.get_mset(0, 0, 4);
1742  TEST_EQUAL(mset.size(), 0);
1743 }
1744 
1746 DEFINE_TESTCASE(freelistleak1, check) {
1747  auto path = get_database_path("freelistleak1",
1748  [](Xapian::WritableDatabase& wdb,
1749  const string&)
1750  {
1751  wdb.set_metadata("foo", "bar");
1752  wdb.commit();
1753  Xapian::Document doc;
1754  doc.add_term("baz");
1755  wdb.add_document(doc);
1756  });
1757  size_t check_errors =
1759  TEST_EQUAL(check_errors, 0);
1760 }
1761 
1763 DEFINE_TESTCASE(splitpostings1, writable) {
1765  Xapian::Document doc;
1766  // Add postings to create a split internally.
1767  for (Xapian::termpos pos = 0; pos <= 100; pos += 10) {
1768  doc.add_posting("foo", pos);
1769  }
1770  for (Xapian::termpos pos = 5; pos <= 100; pos += 20) {
1771  doc.add_posting("foo", pos);
1772  }
1773  db.add_document(doc);
1774  db.commit();
1775 
1776  Xapian::termpos expect = 0;
1777  Xapian::termpos pos = 0;
1778  for (auto p = db.positionlist_begin(1, "foo");
1779  p != db.positionlist_end(1, "foo"); ++p) {
1780  TEST_REL(expect, <=, 100);
1781  pos = *p;
1782  TEST_EQUAL(pos, expect);
1783  expect += 5;
1784  if (expect % 20 == 15) expect += 5;
1785  }
1786  TEST_EQUAL(pos, 100);
1787 }
1788 
1790 DEFINE_TESTCASE(multidb1, backend) {
1791  Xapian::Database db;
1792  TEST_EQUAL(db.size(), 0);
1793  Xapian::Database db2 = get_database("apitest_simpledata");
1794  TEST(db2.size() != 0);
1795  db.add_database(db2);
1796  TEST_EQUAL(db.size(), db2.size());
1797  db.add_database(db2);
1798  // Regression test for bug introduced and fixed in git master before 2.0.0.
1799  // Adding a multi database to an empty database incorrectly worked just
1800  // like assigning the database object. The list of shards is now copied
1801  // instead.
1802  TEST_EQUAL(db.size(), db2.size() * 2);
1804  TEST_EQUAL(db.size(), db2.size() * 2);
1805 }
1806 
1807 // Regression test for bug in unreleased versions before 2.0.0.
1808 DEFINE_TESTCASE(matchall3, backend) {
1809  Xapian::Database db = get_database("apitest_simpledata");
1810  Xapian::Enquire enq(db);
1812  enq.set_query(0 * (Xapian::Query::MatchAll & qw));
1813  TEST_EQUAL(enq.get_mset(0, 10).size(), 0);
1814  enq.set_query(0 * (qw & Xapian::Query::MatchAll));
1815  TEST_EQUAL(enq.get_mset(0, 10).size(), 0);
1816 }
1817 
1818 DEFINE_TESTCASE(reconstruct1, backend) {
1819  Xapian::Database db = get_database("apitest_simpledata");
1821  "and yet anoth this one doe mention banana split "
1822  "though so cant be that bad");
1823  TEST_STRINGS_EQUAL(db.reconstruct_text(1, 14), "this is a test");
1824  TEST_STRINGS_EQUAL(db.reconstruct_text(1, 10, "S"), "");
1825  TEST_STRINGS_EQUAL(db.reconstruct_text(6, 0, "", 1, 3), "and yet anoth");
1826 }
1827 
1828 DEFINE_TESTCASE(reconstruct2, writable) {
1829  Xapian::Database db = get_database("reconstruct2",
1830  [](Xapian::WritableDatabase& wdb,
1831  const string&)
1832  {
1833  Xapian::Document doc;
1834  doc.add_posting("XMBABxyz", 100);
1835  doc.add_posting("XMBABabc", 101);
1836  wdb.add_document(doc);
1837  });
1839 }
1840 
1847 DEFINE_TESTCASE(positfrompostit1, positional) {
1848  Xapian::Database db = get_database("apitest_simpledata");
1849  {
1850  // Wrong results - this was giving (4) instead of (5, 18).
1851  auto postit = db.postlist_begin("paragraph");
1852  TEST_NOT_EQUAL(postit, db.postlist_end("paragraph"));
1853  postit.skip_to(4);
1854  TEST_NOT_EQUAL(postit, db.postlist_end("paragraph"));
1855  auto p = postit.positionlist_begin();
1856  TEST_NOT_EQUAL(p, postit.positionlist_end());
1857  TEST_EQUAL(*p, 5);
1858  ++p;
1859  TEST_NOT_EQUAL(p, postit.positionlist_end());
1860  TEST_EQUAL(*p, 18);
1861  ++p;
1862  TEST_EQUAL(p, postit.positionlist_end());
1863  }
1864  {
1865  // This was giving a segmentation fault.
1866  auto postit = db.postlist_begin("split");
1867  TEST_NOT_EQUAL(postit, db.postlist_end("split"));
1868  postit.skip_to(6);
1869  TEST_NOT_EQUAL(postit, db.postlist_end("split"));
1870  auto p = postit.positionlist_begin();
1871  TEST_NOT_EQUAL(p, postit.positionlist_end());
1872  TEST_EQUAL(*p, 9);
1873  ++p;
1874  TEST_EQUAL(p, postit.positionlist_end());
1875  }
1876 }
1877 
1878 /* Test searching for non-existent terms returns zero results.
1879  *
1880  * Regression test for GlassTable::readahead_key() throwing "Key too long"
1881  * error if passed an oversized key.
1882  */
1883 DEFINE_TESTCASE(nosuchterm, backend) {
1884  Xapian::Database db = get_database("apitest_simpledata");
1885  Xapian::Enquire enquire{db};
1886  // Test up to a length longer than any backend supports.
1887  const unsigned MAX_LEN = 300;
1888  string term;
1889  term.reserve(MAX_LEN);
1890  while (term.size() < MAX_LEN) {
1891  term += 'x';
1892  enquire.set_query(Xapian::Query(term));
1893  TEST_EQUAL(enquire.get_mset(0, 10).size(), 0);
1894  }
1895 }
1896 
1897 // Test that all the terms returned exist.
1898 DEFINE_TESTCASE(allterms7, backend) {
1899  Xapian::Database db = get_database("etext");
1900  for (auto i = db.allterms_begin(); i != db.allterms_end(); ++i) {
1901  string term = *i;
1902  TEST(db.get_termfreq(term) > 0);
1904  }
1905 }
1906 
1907 /* Test that we can't run a second server on the same port.
1908  *
1909  * This testcase is motivated by Microsoft's stupid and insecure semantics for
1910  * SO_REUSEADDR which prior to Server 2003 allowed any process to bind to any
1911  * port, even if it was already in use by a different process (and even if the
1912  * original process hadn't used SO_REUSEADDR.
1913  *
1914  * The external behaviour we check here is that another process can't bind to
1915  * the port xapian-tcpsrv is using, whatever socket options is uses.
1916  *
1917  * We use SO_REUSEADDR on Unix so that a connection in TIME_WAIT state doesn't
1918  * block binding to the socket. Apparently that's the default behaviour on
1919  * Microsoft Windows. Ideally we'd have an external test for this too, but it
1920  * seems hard to write a reliable one.
1921  */
1922 DEFINE_TESTCASE(remoteportreuse1, remotetcp) {
1923  int port;
1924  Xapian::Database db = get_remote_database("apitest_simpledata",
1925  300000,
1926  &port);
1927 
1928  // We test with (up to) 3 different socket options combinations:
1929  //
1930  // 0: no socket options
1931  // 1: SO_REUSEADDR
1932  // 2: SO_EXCLUSIVEADDRUSE (Microsoft-specific option)
1933  //
1934  // We don't test with SO_REUSEADDR and SO_EXCLUSIVEADDRUSE together because
1935  // that's not a valid combination (second setsockopt() call fails with
1936  // WSAEINVAL).
1937  for (int reuse_options : { 0, 1,
1938 #ifdef SO_EXCLUSIVEADDRUSE
1939  2
1940 #endif
1941  }) {
1942  tout << "reuse_options = " << reuse_options << '\n';
1943  int socketfd = -1;
1944  int bind_errno = 0;
1945  for (auto&& r : Resolver("127.0.0.1", port, AI_PASSIVE)) {
1946  int socktype = r.ai_socktype | SOCK_CLOEXEC;
1947  int fd = socket(r.ai_family, socktype, r.ai_protocol);
1948  if (fd == -1)
1949  continue;
1950 
1951  if (reuse_options == 1) {
1952  int on = 1;
1953  // 4th argument might need to be void* or char* - cast it to
1954  // char* since C++ allows implicit conversion to void* but not
1955  // from void*.
1956  int retval = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
1957  reinterpret_cast<char*>(&on),
1958  sizeof(on));
1959 
1960  if (retval < 0) {
1961  int setsockopt_errno = socket_errno();
1962  CLOSESOCKET(fd);
1963  FAIL_TEST("setsockopt SO_REUSEADDR failed " +
1964  errno_to_string(setsockopt_errno));
1965  }
1966 #ifdef SO_EXCLUSIVEADDRUSE
1967  } else if (reuse_options == 2) {
1968  int on = 1;
1969  int retval = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
1970  reinterpret_cast<char*>(&on),
1971  sizeof(on));
1972 
1973  if (retval < 0) {
1974  int setsockopt_errno = socket_errno();
1975  CLOSESOCKET(fd);
1976  FAIL_TEST("setsockopt SO_EXCLUSIVEADDRUSE failed " +
1977  errno_to_string(setsockopt_errno));
1978  }
1979 #endif
1980  }
1981 
1982  if (::bind(fd, r.ai_addr, r.ai_addrlen) == 0) {
1983  socketfd = fd;
1984  break;
1985  }
1986 
1987  // Note down the error code for the first address we try, which
1988  // seems likely to be more helpful than the last in the case where
1989  // they differ.
1990  if (bind_errno == 0)
1991  bind_errno = socket_errno();
1992 
1993  CLOSESOCKET(fd);
1994  }
1995 
1996  if (socketfd >= 0) {
1997  CLOSESOCKET(socketfd);
1998  FAIL_TEST("Managed to bind to TCP port used by xapian-tcpsrv");
1999  }
2000 
2001 #ifdef __WIN32__
2002  // Gives WSAEACCES instead in some cases involving SO_EXCLUSIVEADDRUSE.
2003  if (bind_errno == WSAEACCES) bind_errno = EADDRINUSE;
2004 #endif
2005 
2006  if (bind_errno != EADDRINUSE) {
2007  FAIL_TEST("bind() failed with unexpected error: " +
2008  errno_to_string(bind_errno));
2009  }
2010  }
2011 }
2012 
2013 // Test exception for check() on remote via stub.
2014 DEFINE_TESTCASE(unsupportedcheck1, path) {
2015  mkdir(".stub", 0755);
2016  const char* stubpath = ".stub/unsupportedcheck1";
2017  ofstream out(stubpath);
2018  TEST(out.is_open());
2019  out << "remote :" << BackendManager::get_xapian_progsrv_command()
2020  << ' ' << get_database_path("apitest_simpledata") << '\n';
2021  out.close();
2022 
2023  try {
2024  Xapian::Database::check(stubpath);
2025  FAIL_TEST("Managed to check remote stub");
2026 #ifdef XAPIAN_HAS_REMOTE_BACKEND
2027  } catch (const Xapian::UnimplementedError& e) {
2028  // Check the message is appropriate.
2030  "Remote database checking not implemented");
2031 #else
2032  } catch (const Xapian::FeatureUnavailableError& e) {
2033 #endif
2034  }
2035 }
2036 
2037 // Test exception for check() on inmemory via stub.
2038 DEFINE_TESTCASE(unsupportedcheck2, inmemory) {
2039  mkdir(".stub", 0755);
2040  const char* stubpath = ".stub/unsupportedcheck2";
2041  ofstream out(stubpath);
2042  TEST(out.is_open());
2043  out << "inmemory\n";
2044  out.close();
2045 
2047  Xapian::Database::check(stubpath));
2048 }
2049 
2050 // Test exception for passing empty filename to check().
2051 DEFINE_TESTCASE(unsupportedcheck3, !backend) {
2052  // Regression test, exception was DatabaseOpeningError with description:
2053  // Failed to rewind file descriptor -1 (Bad file descriptor)
2054  try {
2056  } catch (const Xapian::DatabaseOpeningError& e) {
2057  string enoent_msg = errno_to_string(ENOENT);
2058  TEST_EQUAL(e.get_error_string(), enoent_msg);
2059  }
2060 }
2061 
2062 // Test handling of corrupt DB with out of range values in the version file.
2063 // Regression test for #824, fixed in 1.4.25.
2064 DEFINE_TESTCASE(corruptglass1, glass) {
2065  string db_path =
2066  test_driver::get_srcdir() + "/testdata/glass_corrupt_dbX";
2067 
2068  for (int n = '1'; n <= '4'; ++n) {
2069  db_path.back() = n;
2071  Xapian::Database db(db_path));
2072 
2074  Xapian::Database::check(db_path));
2075  }
2076 }
static Xapian::Query query(Xapian::Query::op op, const string &t1=string(), const string &t2=string(), const string &t3=string(), const string &t4=string(), const string &t5=string(), const string &t6=string(), const string &t7=string(), const string &t8=string(), const string &t9=string(), const string &t10=string())
Definition: api_anydb.cc:62
static void make_xordecay1_db(Xapian::WritableDatabase &db, const string &)
Definition: api_backend.cc:772
DEFINE_TESTCASE(lockfileumask1, glass)
Regression test - lockfile should honour umask, was only user-readable.
Definition: api_backend.cc:58
static void make_msize2_db(Xapian::WritableDatabase &db, const string &)
Definition: api_backend.cc:724
static void make_msize1_db(Xapian::WritableDatabase &db, const string &)
Definition: api_backend.cc:674
static void make_phrasebug1_db(Xapian::WritableDatabase &db, const string &)
static void make_ordecay_db(Xapian::WritableDatabase &db, const string &)
Definition: api_backend.cc:800
static void gen_uniqterms_gt_doclen_db(Xapian::WritableDatabase &db, const string &)
static void make_orcheck_db(Xapian::WritableDatabase &db, const string &)
Definition: api_backend.cc:855
std::string get_dbtype()
Definition: apitest.cc:41
Xapian::Database get_writable_database_as_database()
Definition: apitest.cc:126
string get_database_path(const string &dbname)
Definition: apitest.cc:71
Xapian::WritableDatabase get_writable_database(const string &dbname)
Definition: apitest.cc:86
std::string get_named_writable_database_path(const std::string &name)
Definition: apitest.cc:98
Xapian::WritableDatabase get_writable_database_again()
Definition: apitest.cc:132
Xapian::Database get_database(const string &dbname)
Definition: apitest.cc:47
Xapian::WritableDatabase get_named_writable_database(const std::string &name, const std::string &source)
Definition: apitest.cc:92
Xapian::Database get_remote_database(const string &dbname, unsigned int timeout, int *port_ptr)
Definition: apitest.cc:110
test functionality of the Xapian API
#define SKIP_TEST_FOR_BACKEND(B)
Definition: apitest.h:84
Base class for backend handling in test harness.
static const char * get_xapian_progsrv_command()
Get the command line required to run xapian-progsrv.
bool was_called() const
Definition: api_backend.cc:497
bool operator()(const Xapian::Document &) const override
Decide whether to accept a document.
Definition: api_backend.cc:492
Class implementing a "boolean" weighting scheme.
Definition: weight.h:678
Indicates an attempt to access a closed database.
Definition: error.h:1085
DatabaseCorruptError indicates database corruption was detected.
Definition: error.h:397
DatabaseLockError indicates failure to lock a database.
Definition: error.h:481
DatabaseModifiedError indicates a database was modified.
Definition: error.h:527
Indicates an attempt to access a database not present.
Definition: error.h:1043
DatabaseOpeningError indicates failure to open a database.
Definition: error.h:569
An indexed database of documents.
Definition: database.h:75
Xapian::rev get_revision() const
Get the revision of the database.
Definition: database.cc:527
ValueIterator valuestream_begin(Xapian::valueno slot) const
Return an iterator over the value in slot slot for each document.
Definition: database.cc:335
Xapian::doccount get_termfreq(std::string_view term) const
Get the number of documents indexed by a specified term.
Definition: database.cc:262
Xapian::termcount get_unique_terms_lower_bound() const
Get a lower bound on the unique terms size of a document in this DB.
Definition: database.cc:323
static size_t check(std::string_view path, int opts=0, std::ostream *out=NULL)
Check the integrity of a database or database table.
Definition: database.h:669
void close()
Close the database.
Definition: database.cc:99
Xapian::totallength get_total_length() const
Get the total length of all the documents in the database.
Definition: database.cc:256
PositionIterator positionlist_end(Xapian::docid, std::string_view) const noexcept
End iterator corresponding to positionlist_begin().
Definition: database.h:292
Xapian::WritableDatabase lock(int flags=0)
Lock a read-only database for writing.
Definition: database.cc:517
Xapian::termcount get_doclength_lower_bound() const
Get a lower bound on the length of a document in this DB.
Definition: database.cc:302
PostingIterator postlist_begin(std::string_view term) const
Start iterating the postings of a term.
Definition: database.cc:192
std::string reconstruct_text(Xapian::docid did, size_t length=0, std::string_view prefix={}, Xapian::termpos start_pos=0, Xapian::termpos end_pos=0) const
Reconstruct document text.
Definition: database.cc:533
bool locked() const
Test if this database is currently locked for writing.
Definition: database.cc:511
TermIterator termlist_begin(Xapian::docid did) const
Start iterating the terms in a document.
Definition: database.cc:200
double get_avlength() const
Old name for get_average_length() for backward compatibility.
Definition: database.h:322
Xapian::termcount get_wdf_upper_bound(std::string_view term) const
Get an upper bound on the wdf of term term.
Definition: database.cc:314
PositionIterator positionlist_begin(Xapian::docid did, std::string_view term) const
Start iterating positions for a term in a document.
Definition: database.cc:221
size_t size() const
Return number of shards in this Database object.
Definition: database.cc:105
void add_database(const Database &other)
Add shards from another Database.
Definition: database.h:109
Xapian::termcount get_doclength(Xapian::docid did) const
Get the length of a specified document.
Definition: database.cc:341
Xapian::termcount get_unique_terms_upper_bound() const
Get an upper bound on the unique terms size of a document in this DB.
Definition: database.cc:329
TermIterator allterms_end(std::string_view={}) const noexcept
End iterator corresponding to allterms_begin(prefix).
Definition: database.h:307
Xapian::termcount get_collection_freq(std::string_view term) const
Get the total number of occurrences of a specified term.
Definition: database.cc:273
Xapian::doccount get_doccount() const
Get the number of documents in the database.
Definition: database.cc:233
PostingIterator postlist_end(std::string_view) const noexcept
End iterator corresponding to postlist_begin().
Definition: database.h:258
TermIterator termlist_end(Xapian::docid) const noexcept
End iterator corresponding to termlist_begin().
Definition: database.h:271
Xapian::docid get_lastdocid() const
Get the highest document id which has been used in the database.
Definition: database.cc:239
TermIterator allterms_begin(std::string_view prefix={}) const
Start iterating all terms in the database with a given prefix.
Definition: database.cc:209
ValueIterator valuestream_end(Xapian::valueno) const noexcept
Return end iterator corresponding to valuestream_begin().
Definition: database.h:421
bool reopen()
Reopen the database at the latest available revision.
Definition: database.cc:93
Xapian::termcount get_doclength_upper_bound() const
Get an upper bound on the length of a document in this DB.
Definition: database.cc:308
Xapian::Document get_document(Xapian::docid did, unsigned flags=0) const
Get a document from the database.
Definition: database.cc:368
Xapian::Database unlock()
Release a database write lock.
Definition: database.cc:522
Xapian::termcount get_unique_terms(Xapian::docid did) const
Get the number of unique terms in a specified document.
Definition: database.cc:350
Indicates an attempt to access a document not present in the database.
Definition: error.h:662
Class representing a document.
Definition: document.h:64
void add_boolean_term(std::string_view term)
Add a boolean filter term to the document.
Definition: document.h:145
Xapian::docid get_docid() const
Get the document ID this document came from.
Definition: document.cc:69
void set_data(std::string_view data)
Set the document data.
Definition: document.cc:81
std::string get_data() const
Get the document data.
Definition: document.cc:75
void add_term(std::string_view term, Xapian::termcount wdf_inc=1)
Add a term to this document.
Definition: document.cc:87
std::string get_value(Xapian::valueno slot) const
Read a value slot in this document.
Definition: document.cc:185
Xapian::termcount termlist_count() const
Return the number of distinct terms in this document.
Definition: document.cc:174
void clear_terms()
Clear all terms from the document.
Definition: document.cc:168
void add_value(Xapian::valueno slot, std::string_view value)
Add a value to a slot in this document.
Definition: document.cc:191
void add_posting(std::string_view term, Xapian::termpos term_pos, Xapian::termcount wdf_inc=1)
Add a posting for a term.
Definition: document.cc:111
Querying session.
Definition: enquire.h:57
void set_weighting_scheme(const Weight &weight)
Set the weighting scheme to use.
Definition: enquire.cc:85
MSet get_mset(doccount first, doccount maxitems, doccount checkatleast=0, const RSet *rset=NULL, const MatchDecider *mdecider=NULL) const
Run the query.
Definition: enquire.cc:200
void set_query(const Query &query, termcount query_length=0)
Set the query.
Definition: enquire.cc:72
void set_sort_by_value(valueno sort_key, bool reverse)
Set the sorting to be by value only.
Definition: enquire.cc:103
const Query & get_query() const
Get the currently set query.
Definition: enquire.cc:79
void set_collapse_key(valueno collapse_key, doccount collapse_max=1)
Control collapsing of results.
Definition: enquire.cc:165
All exceptions thrown by Xapian are subclasses of Xapian::Error.
Definition: error.h:41
const char * get_error_string() const
Returns any system error string associated with this exception.
Definition: error.cc:50
const std::string & get_msg() const noexcept
Message giving details of the error, intended for human consumption.
Definition: error.h:111
std::string get_description() const
Return a string describing this object.
Definition: error.cc:93
Indicates an attempt to use a feature which is unavailable.
Definition: error.h:707
InvalidArgumentError indicates an invalid parameter value was passed to the API.
Definition: error.h:229
Iterator over a Xapian::MSet.
Definition: mset.h:535
Class representing a list of search results.
Definition: mset.h:46
Xapian::doccount size() const
Return number of items in this MSet object.
Definition: mset.cc:374
Xapian::doccount get_firstitem() const
Rank of first item in this MSet.
Definition: mset.cc:312
Xapian::doccount get_matches_upper_bound() const
Upper bound on the total number of matching documents.
Definition: mset.cc:334
MSetIterator begin() const
Return iterator pointing to the first item in this MSet.
Definition: mset.h:786
Xapian::doccount get_matches_lower_bound() const
Lower bound on the total number of matching documents.
Definition: mset.cc:318
MSetIterator end() const
Return iterator pointing to just after the last item in this MSet.
Definition: mset.h:791
Xapian::doccount get_matches_estimated() const
Estimate of the total number of matching documents.
Definition: mset.cc:324
Abstract base class for match deciders.
Definition: matchdecider.h:37
virtual bool skip_to(Xapian::termpos termpos)=0
Skip forward to the specified position.
Class for iterating over term positions.
void skip_to(Xapian::termpos termpos)
Advance the iterator to term position termpos.
Class for iterating over a list of terms.
Build a Xapian::Query object from a user query string.
Definition: queryparser.h:516
void set_database(const Database &db)
Specify the database being searched.
Definition: queryparser.cc:138
Query parse_query(std::string_view query_string, unsigned flags=FLAG_DEFAULT, std::string_view default_prefix={})
Parse a query.
Definition: queryparser.cc:174
@ FLAG_PARTIAL
Enable partial matching.
Definition: queryparser.h:577
Class representing a query.
Definition: query.h:45
std::string get_description() const
Return a string describing this object.
Definition: query.cc:307
op
Query operators.
Definition: query.h:78
@ OP_MAX
Pick the maximum weight of any subquery.
Definition: query.h:249
@ OP_VALUE_RANGE
Match only documents where a value slot is within a given range.
Definition: query.h:158
@ OP_WILDCARD
Wildcard expansion.
Definition: query.h:255
@ OP_XOR
Match documents which an odd number of subqueries match.
Definition: query.h:107
@ OP_AND_MAYBE
Match the first subquery taking extra weight from other subqueries.
Definition: query.h:118
@ OP_NEAR
Match only documents where all subqueries match near each other.
Definition: query.h:140
@ OP_ELITE_SET
Pick the best N subqueries and combine with OP_OR.
Definition: query.h:215
@ OP_AND
Match only documents which all subqueries match.
Definition: query.h:84
@ OP_OR
Match documents which at least one subquery matches.
Definition: query.h:92
@ OP_FILTER
Match like OP_AND but only taking weight from the first subquery.
Definition: query.h:128
@ OP_PHRASE
Match only documents where all subqueries match near and in order.
Definition: query.h:152
@ OP_SYNONYM
Match like OP_OR but weighting as if a single term.
Definition: query.h:239
@ OP_AND_NOT
Match documents which the first subquery matches but no others do.
Definition: query.h:99
static const Xapian::Query MatchNothing
A query matching no documents.
Definition: query.h:64
static const Xapian::Query MatchAll
A query matching all documents.
Definition: query.h:75
Class for iterating over a list of terms.
Definition: termiterator.h:41
void skip_to(std::string_view term)
Advance the iterator to term term.
Xapian::doccount get_termfreq() const
Return the term frequency for the term at the current position.
UnimplementedError indicates an attempt to use an unimplemented feature.
Definition: error.h:313
Class for iterating over document values.
Definition: valueiterator.h:39
bool check(Xapian::docid docid)
Check if the specified docid occurs.
void skip_to(Xapian::docid docid_or_slot)
Advance the iterator to document id or value slot docid_or_slot.
This class provides read/write access to a database.
Definition: database.h:964
void delete_document(Xapian::docid did)
Delete a document from the database.
Definition: database.cc:567
void replace_document(Xapian::docid did, const Xapian::Document &document)
Replace a document in the database.
Definition: database.cc:582
void set_metadata(std::string_view key, std::string_view metadata)
Set the user-specified metadata associated with a given key.
Definition: database.cc:634
void commit()
Commit pending modifications.
Definition: database.cc:543
Xapian::docid add_document(const Xapian::Document &doc)
Add a document to the database.
Definition: database.cc:561
static std::string get_srcdir()
Read srcdir from environment and if not present, make a valiant attempt to guess a value.
Definition: testsuite.cc:135
string term
PositionList * p
Xapian::termpos pos
void errno_to_string(int e, string &s)
Convert errno value to std::string, thread-safe if possible.
int close(FD &fd)
Definition: fd.h:63
Utility functions for testing files.
bool file_exists(const char *path)
Test if a file exists.
Definition: filetests.h:40
#define false
Definition: header.h:9
static const test_desc tests[]
The lists of tests to perform.
string str(int value)
Convert int to std::string.
Definition: str.cc:91
Database open(std::string_view host, unsigned int port, unsigned timeout=10000, unsigned connect_timeout=10000)
Construct a Database object for read-only access to a remote database accessed via a TCP connection.
const int DB_CREATE
Create a new database.
Definition: constants.h:43
const int DBCHECK_SHOW_STATS
Show statistics for the B-tree.
Definition: constants.h:230
const int DB_RETRY_LOCK
If the database is already locked, retry the lock.
Definition: constants.h:144
const int DB_CREATE_OR_OPEN
Create database if it doesn't already exist.
Definition: constants.h:34
unsigned XAPIAN_TERMCOUNT_BASE_TYPE termcount
A counts of terms.
Definition: types.h:64
const int DB_BACKEND_STUB
Open a stub database file.
Definition: constants.h:166
const int DOC_ASSUME_VALID
Assume document id is valid.
Definition: constants.h:275
const int DB_OPEN
Open an existing database.
Definition: constants.h:49
unsigned XAPIAN_DOCID_BASE_TYPE doccount
A count of documents.
Definition: types.h:37
const int DB_NO_TERMLIST
When creating a database, don't create a termlist table.
Definition: constants.h:135
const int DB_BACKEND_GLASS
Use the glass backend.
Definition: constants.h:157
unsigned XAPIAN_DOCID_BASE_TYPE docid
A unique identifier for a document.
Definition: types.h:51
unsigned XAPIAN_TERMPOS_BASE_TYPE termpos
A term position within a document or query.
Definition: types.h:75
Resolve hostnames and ip addresses.
include <fcntl.h>, but working around broken platforms.
#define O_BINARY
Definition: safefcntl.h:80
include <netdb.h>, with portability workarounds.
include <sys/socket.h> with portability workarounds.
#define SOCK_CLOEXEC
include <sys/stat.h> with portability enhancements
#define S_ISREG(ST_MODE)
Definition: safesysstat.h:59
include <sys/wait.h>, with portability stuff.
<unistd.h>, but with compat.
Socket handling utilities.
#define CLOSESOCKET(S)
Definition: socket_utils.h:123
int socket_errno()
Definition: socket_utils.h:121
Convert types to std::string.
#define TEST_REL(A, REL, B)
Test a relation holds,e.g. TEST_REL(a,>,b);.
Definition: testmacros.h:35
Run multiple tests for different backends.
std::ostringstream tout
The debug printing stream.
Definition: testsuite.cc:104
a generic test suite engine
#define FAIL_TEST(MSG)
Fail the current testcase with message MSG.
Definition: testsuite.h:65
#define SKIP_TEST(MSG)
Skip the current testcase with message MSG.
Definition: testsuite.h:71
#define TEST_EQUAL(a, b)
Test for equality of two things.
Definition: testsuite.h:276
#define TEST_STRINGS_EQUAL(a, b)
Test for equality of two strings.
Definition: testsuite.h:285
#define TEST_EQUAL_DOUBLE(a, b)
Test two doubles for near equality.
Definition: testsuite.h:293
#define TEST(a)
Test a condition, without an additional explanation for failure.
Definition: testsuite.h:273
#define TEST_NOT_EQUAL(a, b)
Test for non-equality of two things.
Definition: testsuite.h:303
void mset_expect_order(const Xapian::MSet &A, Xapian::docid d1, Xapian::docid d2, Xapian::docid d3, Xapian::docid d4, Xapian::docid d5, Xapian::docid d6, Xapian::docid d7, Xapian::docid d8, Xapian::docid d9, Xapian::docid d10, Xapian::docid d11, Xapian::docid d12)
Definition: testutils.cc:224
bool mset_range_is_same(const Xapian::MSet &mset1, unsigned int first1, const Xapian::MSet &mset2, unsigned int first2, unsigned int count)
Definition: testutils.cc:45
Xapian-specific test helper functions and macros.
#define TEST_EXCEPTION(TYPE, CODE)
Check that CODE throws exactly Xapian exception TYPE.
Definition: testutils.h:112
#define TEST_EXCEPTION3(TYPE, CODE, FAIL_TO_THROW_ACTION)
Check that CODE throws exactly Xapian exception TYPE.
Definition: testutils.h:126
void rm_rf(const string &filename)
Remove a directory and contents, just like the Unix "rm -rf" command.
Definition: unixcmds.cc:111
C++ function versions of useful Unix commands.
Public interfaces for the Xapian library.