xapian-core  1.4.27
api_wrdb.cc
Go to the documentation of this file.
1 
4 /* Copyright 1999,2000,2001 BrightStation PLC
5  * Copyright 2001 Hein Ragas
6  * Copyright 2002 Ananova Ltd
7  * Copyright 2002-2023 Olly Betts
8  * Copyright 2006 Richard Boulton
9  * Copyright 2007 Lemur Consulting Ltd
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License as
13  * published by the Free Software Foundation; either version 2 of the
14  * License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
24  * USA
25  */
26 
27 #include <config.h>
28 
29 #include "api_wrdb.h"
30 
31 #include <xapian.h>
32 
33 #include "filetests.h"
34 #include "omassert.h"
35 #include "str.h"
36 #include "stringutils.h"
37 #include "testsuite.h"
38 #include "testutils.h"
39 #include "unixcmds.h"
40 
41 #include "apitest.h"
42 
43 #include "safeunistd.h"
44 #include <cmath>
45 #include <cstdlib>
46 #include <limits>
47 #include <map>
48 #include <string>
49 
50 using namespace std;
51 
52 // #######################################################################
53 // # Tests start here
54 
55 // test that indexing a term more than once at the same position increases
56 // the wdf
57 DEFINE_TESTCASE(adddoc1, writable) {
59 
60  Xapian::Document doc1, doc2, doc3;
61 
62  // doc1 should come top, but if term "foo" gets wdf of 1, doc2 will beat it
63  // doc3 should beat both
64  // Note: all docs have same length
65  doc1.set_data(string("tom"));
66  doc1.add_posting("foo", 1);
67  doc1.add_posting("foo", 1);
68  doc1.add_posting("foo", 1);
69  doc1.add_posting("bar", 3);
70  doc1.add_posting("bar", 4);
71  db.add_document(doc1);
72 
73  doc2.set_data(string("dick"));
74  doc2.add_posting("foo", 1);
75  doc2.add_posting("foo", 2);
76  doc2.add_posting("bar", 3);
77  doc2.add_posting("bar", 3);
78  doc2.add_posting("bar", 3);
79  db.add_document(doc2);
80 
81  doc3.set_data(string("harry"));
82  doc3.add_posting("foo", 1);
83  doc3.add_posting("foo", 1);
84  doc3.add_posting("foo", 2);
85  doc3.add_posting("foo", 2);
86  doc3.add_posting("bar", 3);
87  db.add_document(doc3);
88 
89  Xapian::Query query("foo");
90 
91  Xapian::Enquire enq(db);
92  enq.set_query(query);
93 
94  Xapian::MSet mset = enq.get_mset(0, 10);
95 
96  mset_expect_order(mset, 3, 1, 2);
97 }
98 
99 // test that removing a posting and removing a term works
100 DEFINE_TESTCASE(adddoc2, writable) {
102  // get_termfreq() on a TermIterator from a Document returns the termfreq
103  // for just the shard the doc is in.
104  bool sharded = (db.size() > 1);
105 
106  Xapian::Document doc1;
107 
108  doc1.add_posting("foo", 1);
109  doc1.add_posting("foo", 1);
110  doc1.add_posting("foo", 2);
111  doc1.add_posting("foo", 2);
112  doc1.add_posting("bar", 3);
113  doc1.add_posting("gone", 1);
114  // Quartz had a bug handling a term >= 128 characters longer than the
115  // preceding term in the sort order - this is "foo" + 130 "X"s
116  doc1.add_posting("fooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 1);
117  Xapian::docid did;
118 
119  Xapian::Document doc2 = db.get_document(did = db.add_document(doc1));
120  TEST_EQUAL(did, 1);
121 
122  Xapian::TermIterator iter1 = doc1.termlist_begin();
123  Xapian::TermIterator iter2 = doc2.termlist_begin();
124  TEST(iter1 != doc1.termlist_end());
125  TEST(iter2 != doc2.termlist_end());
126  TEST_EQUAL(*iter1, "bar");
127  TEST_EQUAL(*iter2, *iter1);
128  TEST_EQUAL(iter1.get_wdf(), 1);
129  TEST_EQUAL(iter2.get_wdf(), 1);
130  if (!sharded) {
131  // TEST_EQUAL(iter1.get_termfreq(), 0);
132  TEST_EQUAL(iter2.get_termfreq(), 1);
133  }
134 
135  iter1++;
136  iter2++;
137  TEST(iter1 != doc1.termlist_end());
138  TEST(iter2 != doc2.termlist_end());
139  TEST_EQUAL(*iter1, "foo");
140  TEST_EQUAL(*iter2, *iter1);
141  TEST_EQUAL(iter1.get_wdf(), 4);
142  TEST_EQUAL(iter2.get_wdf(), 4);
143  if (!sharded) {
144  // TEST_EQUAL(iter1.get_termfreq(), 0);
145  TEST_EQUAL(iter2.get_termfreq(), 1);
146  }
147 
148  iter1++;
149  iter2++;
150  TEST(iter1 != doc1.termlist_end());
151  TEST(iter2 != doc2.termlist_end());
152  TEST_EQUAL(*iter1, "fooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
153  TEST_EQUAL(*iter2, *iter1);
154  TEST_EQUAL(iter1.get_wdf(), 1);
155  TEST_EQUAL(iter2.get_wdf(), 1);
156  if (!sharded) {
157  // assertion fails in debug build! TEST_EQUAL(iter1.get_termfreq(), 0);
158  TEST_EQUAL(iter2.get_termfreq(), 1);
159  }
160 
161  iter1++;
162  iter2++;
163  TEST(iter1 != doc1.termlist_end());
164  TEST(iter2 != doc2.termlist_end());
165  TEST_EQUAL(*iter1, "gone");
166  TEST_EQUAL(*iter2, *iter1);
167  TEST_EQUAL(iter1.get_wdf(), 1);
168  TEST_EQUAL(iter2.get_wdf(), 1);
169  if (!sharded) {
170  // assertion fails in debug build! TEST_EQUAL(iter1.get_termfreq(), 0);
171  TEST_EQUAL(iter2.get_termfreq(), 1);
172  }
173 
174  iter1++;
175  iter2++;
176  TEST(iter1 == doc1.termlist_end());
177  TEST(iter2 == doc2.termlist_end());
178 
179  doc2.remove_posting("foo", 1, 5);
180  doc2.add_term("bat", 0);
181  doc2.add_term("bar", 8);
182  doc2.add_term("bag", 0);
183  doc2.remove_term("gone");
184  doc2.remove_term("fooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
185 
186  // Should have (doc,wdf) pairs: (bag,0)(bar,9)(bat,0)(foo,0)
187  // positionlists (bag,none)(bar,3)(bat,none)(foo,2)
188 
189  iter2 = doc2.termlist_begin();
190  TEST(iter2 != doc2.termlist_end());
191  TEST_EQUAL(*iter2, "bag");
192  // TEST_EQUAL(iter2.get_termfreq(), 0);
193  iter2++;
194  TEST(iter2 != doc2.termlist_end());
195  TEST_EQUAL(*iter2, "bar");
196  // TEST_EQUAL(iter2.get_termfreq(), 0);
197  iter2++;
198  TEST(iter2 != doc2.termlist_end());
199  TEST_EQUAL(*iter2, "bat");
200  // TEST_EQUAL(iter2.get_termfreq(), 0);
201  iter2++;
202  TEST(iter2 != doc2.termlist_end());
203  TEST_EQUAL(*iter2, "foo");
204  // TEST_EQUAL(iter2.get_termfreq(), 0);
205  iter2++;
206  TEST(iter2 == doc2.termlist_end());
207 
208  doc1 = db.get_document(did = db.add_document(doc2));
209  TEST_EQUAL(did, 2);
210 
211  iter1 = doc1.termlist_begin();
212  iter2 = doc2.termlist_begin();
213  TEST(iter1 != doc1.termlist_end());
214  TEST(iter2 != doc2.termlist_end());
215  TEST_EQUAL(*iter1, "bag");
216  TEST_EQUAL(*iter2, *iter1);
217  TEST_EQUAL(iter1.get_wdf(), 0);
218  TEST_EQUAL(iter2.get_wdf(), 0);
219  if (!sharded) {
220  TEST_EQUAL(iter1.get_termfreq(), 1);
221  // TEST_EQUAL(iter2.get_termfreq(), 0);
222  }
223  TEST(iter1.positionlist_begin() == iter1.positionlist_end());
224  TEST(iter2.positionlist_begin() == iter2.positionlist_end());
225 
226  iter1++;
227  iter2++;
228  TEST(iter1 != doc1.termlist_end());
229  TEST(iter2 != doc2.termlist_end());
230  TEST_EQUAL(*iter1, "bar");
231  TEST_EQUAL(*iter2, *iter1);
232  TEST_EQUAL(iter1.get_wdf(), 9);
233  TEST_EQUAL(iter2.get_wdf(), 9);
234  if (!sharded) {
235  TEST_EQUAL(iter1.get_termfreq(), 2);
236  // TEST_EQUAL(iter2.get_termfreq(), 0);
237  }
238 
240  pi1 = iter1.positionlist_begin();
242  TEST_EQUAL(*pi1, 3); pi1++;
243  TEST_EQUAL(*pi2, 3); pi2++;
244  TEST(pi1 == iter1.positionlist_end());
245  TEST(pi2 == iter2.positionlist_end());
246 
247  iter1++;
248  iter2++;
249  TEST(iter1 != doc1.termlist_end());
250  TEST(iter2 != doc2.termlist_end());
251  TEST_EQUAL(*iter1, "bat");
252  TEST_EQUAL(*iter2, *iter1);
253  TEST_EQUAL(iter1.get_wdf(), 0);
254  TEST_EQUAL(iter2.get_wdf(), 0);
255  if (!sharded) {
256  TEST_EQUAL(iter1.get_termfreq(), 1);
257  // TEST_EQUAL(iter2.get_termfreq(), 0);
258  }
259  TEST(iter1.positionlist_begin() == iter1.positionlist_end());
260  TEST(iter2.positionlist_begin() == iter2.positionlist_end());
261 
262  iter1++;
263  iter2++;
264  TEST(iter1 != doc1.termlist_end());
265  TEST(iter2 != doc2.termlist_end());
266  TEST_EQUAL(*iter1, "foo");
267  TEST_EQUAL(*iter2, *iter1);
268  TEST_EQUAL(iter1.get_wdf(), 0);
269  TEST_EQUAL(iter2.get_wdf(), 0);
270  if (!sharded) {
271  TEST_EQUAL(iter1.get_termfreq(), 2);
272  // TEST_EQUAL(iter2.get_termfreq(), 0);
273  }
274 
275  Xapian::PositionIterator temp1 = iter1.positionlist_begin();
276  pi1 = temp1;
278  pi2 = temp2;
279  TEST_EQUAL(*pi1, 2); pi1++;
280  TEST_EQUAL(*pi2, 2); pi2++;
281  TEST(pi1 == iter1.positionlist_end());
282  TEST(pi2 == iter2.positionlist_end());
283 
284  iter1++;
285  iter2++;
286  TEST(iter1 == doc1.termlist_end());
287  TEST(iter2 == doc2.termlist_end());
288 }
289 
290 // test that adding lots of documents works, and doesn't leak memory
291 // REGRESSION FIXED:2003-09-07
292 DEFINE_TESTCASE(adddoc3, writable) {
294 
295  for (Xapian::doccount i = 0; i < 2100; ++i) {
296  Xapian::Document doc;
297  for (Xapian::termcount t = 0; t < 100; ++t) {
298  string term("foo");
299  term += char(t ^ 70 ^ i);
300  doc.add_posting(term, t);
301  }
302  db.add_document(doc);
303  }
304 }
305 
306 // We originally wanted to test that a termlist starting with a 48 character
307 // long term worked since that required special handling in flint for
308 // historical reasons. That's no longer relevant, but it seems useful to
309 // continue to test term lists starting with various term lengths work.
310 DEFINE_TESTCASE(adddoc4, writable) {
312 
313  for (Xapian::doccount i = 1; i <= 240; ++i) {
314  Xapian::Document doc;
315  string term(i, 'X');
316  doc.add_term(term);
317  db.add_document(doc);
318  }
320  db.commit();
321 
322  for (Xapian::doccount i = 1; i <= 240; ++i) {
323  Xapian::Document doc = db.get_document(i);
325  TEST(t != doc.termlist_end());
326  TEST_EQUAL((*t).size(), i);
327  ++t;
328  TEST(t == doc.termlist_end());
329  }
330 
331  // And test a document with no terms.
332  Xapian::Document doc = db.get_document(241);
333  TEST(doc.termlist_begin() == doc.termlist_end());
334 }
335 
336 // Test adding a document, and checking that it got added correctly.
337 // This testcase used to be adddoc2 in quartztest.
338 DEFINE_TESTCASE(adddoc5, writable) {
339  // FIXME: With multi, get_termfreq() on a TermIterator from a Document
340  // currently returns the termfreq for just the shard the doc is in.
341 
342  // Inmemory doesn't support get_writable_database_as_database().
343  SKIP_TEST_FOR_BACKEND("inmemory");
344 
345  Xapian::docid did;
346  Xapian::Document document_in;
347  document_in.set_data("Foobar rising");
348  document_in.add_value(7, "Value7");
349  document_in.add_value(13, "Value13");
350  document_in.add_posting("foobar", 1);
351  document_in.add_posting("rising", 2);
352  document_in.add_posting("foobar", 3);
353 
354  Xapian::Document document_in2;
355  document_in2.set_data("Foobar falling");
356  document_in2.add_posting("foobar", 1);
357  document_in2.add_posting("falling", 2);
358  {
360 
361  TEST_EQUAL(database.get_doccount(), 0);
362  TEST_EQUAL(database.get_avlength(), 0);
363 
364  did = database.add_document(document_in);
365  TEST_EQUAL(database.get_doccount(), 1);
366  TEST_EQUAL(database.get_avlength(), 3);
367 
368  TEST_EQUAL(database.get_termfreq("foobar"), 1);
369  TEST_EQUAL(database.get_collection_freq("foobar"), 2);
370  TEST_EQUAL(database.get_termfreq("rising"), 1);
371  TEST_EQUAL(database.get_collection_freq("rising"), 1);
372  TEST_EQUAL(database.get_termfreq("falling"), 0);
373  TEST_EQUAL(database.get_collection_freq("falling"), 0);
374 
375  Xapian::docid did2 = database.add_document(document_in2);
376  TEST_EQUAL(database.get_doccount(), 2);
377  TEST_NOT_EQUAL(did, did2);
378  TEST_EQUAL(database.get_avlength(), 5.0 / 2.0);
379 
380  TEST_EQUAL(database.get_termfreq("foobar"), 2);
381  TEST_EQUAL(database.get_collection_freq("foobar"), 3);
382  TEST_EQUAL(database.get_termfreq("rising"), 1);
383  TEST_EQUAL(database.get_collection_freq("rising"), 1);
384  TEST_EQUAL(database.get_termfreq("falling"), 1);
385  TEST_EQUAL(database.get_collection_freq("falling"), 1);
386 
387  database.delete_document(did);
388  TEST_EQUAL(database.get_doccount(), 1);
389  TEST_EQUAL(database.get_avlength(), 2);
390 
391  TEST_EQUAL(database.get_termfreq("foobar"), 1);
392  TEST_EQUAL(database.get_collection_freq("foobar"), 1);
393  TEST_EQUAL(database.get_termfreq("rising"), 0);
394  TEST_EQUAL(database.get_collection_freq("rising"), 0);
395  TEST_EQUAL(database.get_termfreq("falling"), 1);
396  TEST_EQUAL(database.get_collection_freq("falling"), 1);
397 
398  did = database.add_document(document_in);
399  TEST_EQUAL(database.get_doccount(), 2);
400  TEST_EQUAL(database.get_avlength(), 5.0 / 2.0);
401 
402  TEST_EQUAL(database.get_termfreq("foobar"), 2);
403  TEST_EQUAL(database.get_collection_freq("foobar"), 3);
404  TEST_EQUAL(database.get_termfreq("rising"), 1);
405  TEST_EQUAL(database.get_collection_freq("rising"), 1);
406  TEST_EQUAL(database.get_termfreq("falling"), 1);
407  TEST_EQUAL(database.get_collection_freq("falling"), 1);
408  }
409 
410  {
412  Xapian::Document document_out = database.get_document(did);
413 
414  // get_termfreq() on a TermIterator from a Document returns the
415  // termfreq for just the shard the doc is in.
416  bool sharded = (database.size() > 1);
417 
418  TEST_EQUAL(document_in.get_data(), document_out.get_data());
419 
420  {
421  Xapian::ValueIterator i(document_in.values_begin());
422  Xapian::ValueIterator j(document_out.values_begin());
423  for (; i != document_in.values_end(); i++, j++) {
424  TEST_NOT_EQUAL(j, document_out.values_end());
425  TEST_EQUAL(*i, *j);
426  TEST_EQUAL(i.get_valueno(), j.get_valueno());
427  }
428  TEST_EQUAL(j, document_out.values_end());
429  }
430 
431  {
432  // Regression test for bug fixed in 1.0.5 - values_begin() didn't
433  // ensure that values had been read. However, values_end() did
434  // (and so did values_count()) so this wasn't generally an issue
435  // but it shouldn't happen anyway.
436  Xapian::Document doc_tmp = database.get_document(did);
437  Xapian::ValueIterator i = document_in.values_begin();
438  Xapian::ValueIterator j = doc_tmp.values_begin();
439  TEST_EQUAL(*i, *j);
440  }
441 
442  {
443  Xapian::TermIterator i(document_in.termlist_begin());
444  Xapian::TermIterator j(document_out.termlist_begin());
445  for (; i != document_in.termlist_end(); i++, j++) {
446  TEST_NOT_EQUAL(j, document_out.termlist_end());
447  TEST_EQUAL(*i, *j);
448  TEST_EQUAL(i.get_wdf(), j.get_wdf());
449  if (!sharded) {
450  // Actually use termfreq to stop compiler optimising away the
451  // call to get_termfreq().
453  if (i.get_termfreq()) FAIL_TEST("?"));
454  TEST_NOT_EQUAL(0, j.get_termfreq());
455  if (*i == "foobar") {
456  // termfreq of foobar is 2
457  TEST_EQUAL(2, j.get_termfreq());
458  } else {
459  // termfreq of rising is 1
460  TEST_EQUAL(*i, "rising");
461  TEST_EQUAL(1, j.get_termfreq());
462  }
463  }
464  Xapian::PositionIterator k(i.positionlist_begin());
465  Xapian::PositionIterator l(j.positionlist_begin());
466  for (; k != i.positionlist_end(); k++, l++) {
467  TEST_NOT_EQUAL(l, j.positionlist_end());
468  TEST_EQUAL(*k, *l);
469  }
470  TEST_EQUAL(l, j.positionlist_end());
471  }
472  TEST_EQUAL(j, document_out.termlist_end());
473  }
474  }
475 }
476 
477 // Test adding a document, and checking that it got added correctly.
478 // This testcase used to be adddoc3 in quartztest.
479 DEFINE_TESTCASE(adddoc6, writable) {
480  // Inmemory doesn't support get_writable_database_again().
481  SKIP_TEST_FOR_BACKEND("inmemory");
482 
483  Xapian::docid did;
484  Xapian::Document document_in;
485  document_in.set_data("Foobar rising");
486  document_in.add_value(7, "Value7");
487  document_in.add_value(13, "Value13");
488  document_in.add_posting("foo", 1);
489  document_in.add_posting("bar", 2);
490 
491  {
493 
494  did = database.add_document(document_in);
495  TEST_EQUAL(did, 1);
496  TEST_EQUAL(database.get_doccount(), 1);
497  TEST_EQUAL(database.get_avlength(), 2);
498  }
499 
500  {
502 
503  document_in.remove_term("foo");
504  document_in.add_posting("baz", 1);
505 
506  database.replace_document(1, document_in);
507 
508  database.delete_document(1);
509 
510  TEST_EQUAL(database.get_doccount(), 0);
511  TEST_EQUAL(database.get_avlength(), 0);
512  TEST_EQUAL(database.get_termfreq("foo"), 0);
513  TEST_EQUAL(database.get_collection_freq("foo"), 0);
514  TEST_EQUAL(database.get_termfreq("bar"), 0);
515  TEST_EQUAL(database.get_collection_freq("bar"), 0);
516  TEST_EQUAL(database.get_termfreq("baz"), 0);
517  TEST_EQUAL(database.get_collection_freq("baz"), 0);
518  }
519 }
520 
521 // tests that database destructors commit if it isn't done explicitly
522 DEFINE_TESTCASE(implicitendsession1, writable) {
524 
525  Xapian::Document doc;
526 
527  doc.set_data(string("top secret"));
528  doc.add_posting("cia", 1);
529  doc.add_posting("nsa", 2);
530  doc.add_posting("fbi", 3);
531  db.add_document(doc);
532 }
533 
534 // tests that assignment of Xapian::Database and Xapian::WritableDatabase works
535 // as expected
536 DEFINE_TESTCASE(databaseassign1, writable) {
539  Xapian::Database actually_wdb = wdb;
540  Xapian::WritableDatabase w1(wdb);
541  w1 = wdb;
542  Xapian::Database d1(wdb);
543  Xapian::Database d2(actually_wdb);
544  d2 = wdb;
545  d2 = actually_wdb;
546 #ifdef __has_warning
547 # if __has_warning("-Wself-assign-overloaded")
548  // Suppress warning from newer clang about self-assignment so we can
549  // test that self-assignment works!
550 # pragma clang diagnostic push
551 # pragma clang diagnostic ignored "-Wself-assign-overloaded"
552 # endif
553 #endif
554  wdb = wdb; // check assign to itself works
555  db = db; // check assign to itself works
556 #ifdef __has_warning
557 # if __has_warning("-Wself-assign-overloaded")
558 # pragma clang diagnostic pop
559 # endif
560 #endif
561 }
562 
563 // tests that deletion and updating of documents works as expected
564 DEFINE_TESTCASE(deldoc1, writable) {
566 
567  Xapian::Document doc1;
568 
569  doc1.add_posting("foo", 1);
570  doc1.add_posting("foo", 1);
571  doc1.add_posting("foo", 2);
572  doc1.add_posting("foo", 2);
573  doc1.add_posting("bar", 3);
574  doc1.add_posting("gone", 1);
575 
576  Xapian::docid did = db.add_document(doc1);
577  TEST_EQUAL(did, 1);
578 
579  doc1.remove_term("gone");
580 
581  did = db.add_document(doc1);
582  TEST_EQUAL(did, 2);
583 
584  doc1.add_term("new", 1);
585  did = db.add_document(doc1);
586  TEST_EQUAL(did, 3);
587 
588  db.delete_document(1);
589 
591 
592  doc1 = db.get_document(2);
593  doc1.remove_term("foo");
594  doc1.add_term("fwing");
595  db.replace_document(2, doc1);
596 
597  Xapian::Document doc2 = db.get_document(2);
599  TEST_NOT_EQUAL(tit, doc2.termlist_end());
600  TEST_EQUAL(*tit, "bar");
601  tit++;
602  TEST_NOT_EQUAL(tit, doc2.termlist_end());
603  TEST_EQUAL(*tit, "fwing");
604  tit++;
605  TEST_EQUAL(tit, doc2.termlist_end());
606 }
607 
608 // tests that deletion and updating of documents works as expected
609 DEFINE_TESTCASE(deldoc2, writable) {
611 
612  Xapian::Document doc1;
613 
614  doc1.add_posting("one", 1);
615  doc1.add_posting("two", 2);
616  doc1.add_posting("two", 3);
617  Xapian::docid did;
618 
619  did = db.add_document(doc1);
620  TEST_EQUAL(did, 1);
621 
622  doc1.remove_term("one");
623  doc1.add_posting("three", 4);
624 
625  did = db.add_document(doc1);
626  TEST_EQUAL(did, 2);
627 
628  doc1.add_posting("one", 7);
629  doc1.remove_term("two");
630 
631  did = db.add_document(doc1);
632  TEST_EQUAL(did, 3);
633 
634  db.commit();
635 
636  // reopen() on a writable database shouldn't do anything.
637  TEST(!db.reopen());
638 
639  db.delete_document(1);
640  db.delete_document(2);
641  db.delete_document(3);
642 
643  db.commit();
644 
645  // reopen() on a writable database shouldn't do anything.
646  TEST(!db.reopen());
647 
648  TEST_EQUAL(db.postlist_begin("one"), db.postlist_end("one"));
649  TEST_EQUAL(db.postlist_begin("two"), db.postlist_end("two"));
650  TEST_EQUAL(db.postlist_begin("three"), db.postlist_end("three"));
651 
656 
657  // test positionlist_{begin,end}?
658 
659  TEST_EQUAL(db.get_doccount(), 0);
660  TEST_EQUAL(db.get_avlength(), 0);
661  TEST_EQUAL(db.get_termfreq("one"), 0);
662  TEST_EQUAL(db.get_termfreq("two"), 0);
663  TEST_EQUAL(db.get_termfreq("three"), 0);
664 
665  TEST(!db.term_exists("one"));
666  TEST(!db.term_exists("two"));
667  TEST(!db.term_exists("three"));
668 
669  TEST_EQUAL(db.get_collection_freq("one"), 0);
670  TEST_EQUAL(db.get_collection_freq("two"), 0);
671  TEST_EQUAL(db.get_collection_freq("three"), 0);
672 
676 
680 
684 
686 }
687 
688 // another test of deletion of documents, a cut-down version of deldoc2
689 DEFINE_TESTCASE(deldoc3, writable) {
691 
692  Xapian::Document doc1;
693 
694  doc1.add_posting("one", 1);
695 
696  Xapian::docid did = db.add_document(doc1);
697  TEST_EQUAL(did, 1);
698 
699  db.commit();
700 
701  // reopen() on a writable database shouldn't do anything.
702  TEST(!db.reopen());
703 
704  db.delete_document(1);
705 
706  db.commit();
707 
708  // reopen() on a writable database shouldn't do anything.
709  TEST(!db.reopen());
710 
711  TEST_EQUAL(db.postlist_begin("one"), db.postlist_end("one"));
712 
715 
716  // test positionlist_{begin,end}?
717 
718  TEST_EQUAL(db.get_doccount(), 0);
719  TEST_EQUAL(db.get_avlength(), 0);
720  TEST_EQUAL(db.get_termfreq("one"), 0);
721 
722  TEST(!db.term_exists("one"));
723 
724  TEST_EQUAL(db.get_collection_freq("one"), 0);
725 
728 
731 
734 
736 }
737 
738 // tests that deletion and updating of (lots of) documents works as expected
739 DEFINE_TESTCASE(deldoc4, writable) {
741 
742  Xapian::Document doc1;
743 
744  doc1.add_posting("one", 1);
745  doc1.add_posting("two", 2);
746  doc1.add_posting("two", 3);
747 
748  Xapian::Document doc2 = doc1;
749  doc2.remove_term("one");
750  doc2.add_posting("three", 4);
751 
752  Xapian::Document doc3 = doc2;
753  doc3.add_posting("one", 7);
754  doc3.remove_term("two");
755 
756  const Xapian::docid maxdoc = 1000 * 3;
757  Xapian::docid did;
758  for (Xapian::docid i = 0; i < maxdoc / 3; ++i) {
759  did = db.add_document(doc1);
760  TEST_EQUAL(did, i * 3 + 1);
761  did = db.add_document(doc2);
762  TEST_EQUAL(did, i * 3 + 2);
763  did = db.add_document(doc3);
764  TEST_EQUAL(did, i * 3 + 3);
765 
766  bool is_power_of_two = ((i & (i - 1)) == 0);
767  if (is_power_of_two) {
768  db.commit();
769  // reopen() on a writable database shouldn't do anything.
770  TEST(!db.reopen());
771  }
772  }
773  db.commit();
774  // reopen() on a writable database shouldn't do anything.
775  TEST(!db.reopen());
776 
777  /* delete the documents in a peculiar order */
778  for (Xapian::docid i = 0; i < maxdoc / 3; ++i) {
779  db.delete_document(maxdoc - i);
780  db.delete_document(maxdoc / 3 + i + 1);
781  db.delete_document(i + 1);
782  }
783 
784  db.commit();
785  // reopen() on a writable database shouldn't do anything.
786  TEST(!db.reopen());
787 
788  TEST_EQUAL(db.postlist_begin("one"), db.postlist_end("one"));
789  TEST_EQUAL(db.postlist_begin("two"), db.postlist_end("two"));
790  TEST_EQUAL(db.postlist_begin("three"), db.postlist_end("three"));
791 
792  for (Xapian::docid i = 1; i <= maxdoc; ++i) {
793  // TEST_EXCEPTION writes to tout each time if the test is run
794  // in verbose mode and some string stream implementations get
795  // very inefficient with large strings, so clear tout on each pass of
796  // the loop to speed up the test since the older information isn't
797  // interesting anyway.
798  tout.str(string());
803  }
804 
805  // test positionlist_{begin,end}?
806 
807  TEST_EQUAL(db.get_doccount(), 0);
808  TEST_EQUAL(db.get_avlength(), 0);
809  TEST_EQUAL(db.get_termfreq("one"), 0);
810  TEST_EQUAL(db.get_termfreq("two"), 0);
811  TEST_EQUAL(db.get_termfreq("three"), 0);
812 
813  TEST(!db.term_exists("one"));
814  TEST(!db.term_exists("two"));
815  TEST(!db.term_exists("three"));
816 
817  TEST_EQUAL(db.get_collection_freq("one"), 0);
818  TEST_EQUAL(db.get_collection_freq("two"), 0);
819  TEST_EQUAL(db.get_collection_freq("three"), 0);
820 
822 }
823 
824 // Test deleting a document which was added in the same batch.
825 DEFINE_TESTCASE(deldoc5, writable) {
827 
828  Xapian::Document doc1;
829 
830  doc1.add_posting("foo", 1);
831  doc1.add_posting("bar", 2);
832  doc1.add_posting("aardvark", 3);
833 
834  Xapian::docid did = db.add_document(doc1);
835  TEST_EQUAL(did, 1);
836 
837  doc1.remove_term("bar");
838  doc1.add_term("hello");
839 
840  did = db.add_document(doc1);
841  TEST_EQUAL(did, 2);
842 
843  doc1.add_term("world", 1);
844  did = db.add_document(doc1);
845  TEST_EQUAL(did, 3);
846 
847  db.delete_document(2);
848 
850 
851  db.commit();
852 
854 
855  TEST_EQUAL(db.get_termfreq("foo"), 2);
856  TEST_EQUAL(db.get_termfreq("aardvark"), 2);
857  TEST_EQUAL(db.get_termfreq("hello"), 1);
858 
860  TEST_NOT_EQUAL(p, db.postlist_end("foo"));
861  TEST_EQUAL(*p, 1);
862  ++p;
863  TEST_NOT_EQUAL(p, db.postlist_end("foo"));
864  TEST_EQUAL(*p, 3);
865  ++p;
866  TEST_EQUAL(p, db.postlist_end("foo"));
867 }
868 
869 // Regression test for bug in quartz and flint, fixed in 1.0.2.
870 DEFINE_TESTCASE(deldoc6, writable) {
872 
873  Xapian::Document doc1;
874 
875  doc1.add_posting("foo", 1);
876  doc1.add_posting("bar", 2);
877  doc1.add_posting("aardvark", 3);
878 
879  Xapian::docid did = db.add_document(doc1);
880  TEST_EQUAL(did, 1);
881 
882  doc1.remove_term("bar");
883  doc1.add_term("hello");
884 
885  did = db.add_document(doc1);
886  TEST_EQUAL(did, 2);
887 
888  db.commit();
889 
890  db.delete_document(2);
892 
893  db.commit();
894 
896 }
897 
898 DEFINE_TESTCASE(replacedoc1, writable) {
900 
901  Xapian::Document doc1;
902 
903  doc1.add_posting("foo", 1);
904  doc1.add_posting("foo", 2);
905  doc1.add_posting("gone", 3);
906  doc1.add_posting("bar", 4);
907  doc1.add_posting("foo", 5);
908  Xapian::docid did;
909 
910  did = db.add_document(doc1);
911  TEST_EQUAL(did, 1);
912 
913  Xapian::Document doc2;
914 
915  doc2.add_posting("foo", 1);
916  doc2.add_posting("pipco", 2);
917  doc2.add_posting("bar", 4);
918  doc2.add_posting("foo", 5);
919 
920  db.replace_document(did, doc2);
921 
922  Xapian::Document doc3 = db.get_document(did);
923  Xapian::TermIterator t_iter = doc3.termlist_begin();
924  TEST_EQUAL(*t_iter, "bar");
926  TEST_EQUAL(*p_iter, 4);
927  ++t_iter;
928  TEST_EQUAL(*t_iter, "foo");
930  TEST_EQUAL(*q_iter, 1);
931  ++q_iter;
932  TEST_EQUAL(*q_iter, 5);
933  ++t_iter;
934  TEST_EQUAL(*t_iter, "pipco");
936  TEST_EQUAL(*r_iter, 2);
937  ++t_iter;
938  TEST_EQUAL(t_iter, doc3.termlist_end());
939 }
940 
941 // Test of new feature: WritableDatabase::replace_document accepts a docid
942 // which doesn't yet exist as of Xapian 0.8.2.
943 DEFINE_TESTCASE(replacedoc2, writable) {
945 
946  Xapian::Document doc1;
947 
948  doc1.add_posting("foo", 1);
949  doc1.add_posting("foo", 2);
950  doc1.add_posting("gone", 3);
951  doc1.add_posting("bar", 4);
952  doc1.add_posting("foo", 5);
953  Xapian::docid did = 31770;
954 
955  db.replace_document(did, doc1);
956 
957  // Regression tests for bug in the InMemory backend - fixed in 1.0.2.
959  Xapian::PostingIterator postit = db.postlist_begin("");
960  TEST(postit != db.postlist_end(""));
961  TEST_EQUAL(*postit, 31770);
962 
963  Xapian::Document doc2;
964 
965  doc2.add_posting("foo", 1);
966  doc2.add_posting("pipco", 2);
967  doc2.add_posting("bar", 4);
968  doc2.add_posting("foo", 5);
969 
970  db.replace_document(did, doc2);
971  TEST_EQUAL(db.get_doccount(), 1);
972 
973  Xapian::Document doc3 = db.get_document(did);
974  Xapian::TermIterator t_iter = doc3.termlist_begin();
975  TEST_EQUAL(*t_iter, "bar");
977  TEST_EQUAL(*p_iter, 4);
978  ++t_iter;
979  TEST_EQUAL(*t_iter, "foo");
981  TEST_EQUAL(*q_iter, 1);
982  ++q_iter;
983  TEST_EQUAL(*q_iter, 5);
984  ++t_iter;
985  TEST_EQUAL(*t_iter, "pipco");
987  TEST_EQUAL(*r_iter, 2);
988  ++t_iter;
989  TEST_EQUAL(t_iter, doc3.termlist_end());
990 
991  did = db.add_document(doc1);
992  TEST_EQUAL(did, 31771);
993  TEST_EQUAL(db.get_doccount(), 2);
994 
996 }
997 
998 // Test replacing a document which was added in the same batch.
999 DEFINE_TESTCASE(replacedoc3, writable) {
1001 
1002  Xapian::Document doc1;
1003 
1004  doc1.add_posting("foo", 1);
1005  doc1.add_posting("bar", 2);
1006  doc1.add_posting("aardvark", 3);
1007 
1008  Xapian::docid did = db.add_document(doc1);
1009  TEST_EQUAL(did, 1);
1010 
1011  doc1.remove_term("bar");
1012  doc1.add_term("hello");
1013 
1014  did = db.add_document(doc1);
1015  TEST_EQUAL(did, 2);
1016 
1017  doc1.add_term("world", 1);
1018  did = db.add_document(doc1);
1019  TEST_EQUAL(did, 3);
1020 
1021  Xapian::Document doc2;
1022  doc2.add_term("world");
1023  db.replace_document(2, doc2);
1024 
1025  db.commit();
1026 
1027  // Check that the document exists (no DocNotFoundError).
1028  doc2 = db.get_document(2);
1029 
1030  TEST_EQUAL(db.get_termfreq("foo"), 2);
1031  TEST_EQUAL(db.get_termfreq("aardvark"), 2);
1032  TEST_EQUAL(db.get_termfreq("hello"), 1);
1033  TEST_EQUAL(db.get_termfreq("world"), 2);
1034 
1035  TEST_EQUAL(db.get_doclength(1), 3);
1036  TEST_EQUAL(db.get_doclength(2), 1);
1037  TEST_EQUAL(db.get_doclength(3), 4);
1038 
1039  TEST_EQUAL(db.get_unique_terms(1), 3);
1040  TEST_EQUAL(db.get_unique_terms(2), 1);
1041  TEST_EQUAL(db.get_unique_terms(3), 4);
1042 
1044  TEST_NOT_EQUAL(p, db.postlist_end("foo"));
1045  TEST_EQUAL(*p, 1);
1046  TEST_EQUAL(p.get_doclength(), 3);
1047  TEST_EQUAL(p.get_unique_terms(), 3);
1048  ++p;
1049  TEST_NOT_EQUAL(p, db.postlist_end("foo"));
1050  TEST_EQUAL(*p, 3);
1051  TEST_EQUAL(p.get_doclength(), 4);
1052  TEST_EQUAL(p.get_unique_terms(), 4);
1053  ++p;
1054  TEST_EQUAL(p, db.postlist_end("foo"));
1055 
1056  p = db.postlist_begin("world");
1057  TEST_NOT_EQUAL(p, db.postlist_end("world"));
1058  TEST_EQUAL(*p, 2);
1059  TEST_EQUAL(p.get_doclength(), 1);
1060  TEST_EQUAL(p.get_unique_terms(), 1);
1061  ++p;
1062  TEST_NOT_EQUAL(p, db.postlist_end("world"));
1063  TEST_EQUAL(*p, 3);
1064  TEST_EQUAL(p.get_doclength(), 4);
1065  TEST_EQUAL(p.get_unique_terms(), 4);
1066  ++p;
1067  TEST_EQUAL(p, db.postlist_end("world"));
1068 }
1069 
1070 // Test replacing a document which was deleted in the same batch.
1071 DEFINE_TESTCASE(replacedoc4, writable) {
1073 
1074  Xapian::Document doc1;
1075 
1076  doc1.add_posting("foo", 1);
1077  doc1.add_posting("bar", 2);
1078  doc1.add_posting("aardvark", 3);
1079 
1080  Xapian::docid did = db.add_document(doc1);
1081  TEST_EQUAL(did, 1);
1082 
1083  doc1.remove_term("bar");
1084  doc1.add_term("hello");
1085 
1086  did = db.add_document(doc1);
1087  TEST_EQUAL(did, 2);
1088 
1089  doc1.add_term("world", 1);
1090  did = db.add_document(doc1);
1091  TEST_EQUAL(did, 3);
1092 
1093  db.delete_document(2);
1094 
1095  Xapian::Document doc2;
1096  doc2.add_term("world");
1097  db.replace_document(2, doc2);
1098 
1099  db.commit();
1100 
1101  // Check that the document exists (no DocNotFoundError).
1102  doc2 = db.get_document(2);
1103 
1104  TEST_EQUAL(db.get_termfreq("foo"), 2);
1105  TEST_EQUAL(db.get_termfreq("aardvark"), 2);
1106  TEST_EQUAL(db.get_termfreq("hello"), 1);
1107  TEST_EQUAL(db.get_termfreq("world"), 2);
1108 
1110  TEST_NOT_EQUAL(p, db.postlist_end("foo"));
1111  TEST_EQUAL(*p, 1);
1112  ++p;
1113  TEST_NOT_EQUAL(p, db.postlist_end("foo"));
1114  TEST_EQUAL(*p, 3);
1115  ++p;
1116  TEST_EQUAL(p, db.postlist_end("foo"));
1117 
1118  p = db.postlist_begin("world");
1119  TEST_NOT_EQUAL(p, db.postlist_end("world"));
1120  TEST_EQUAL(*p, 2);
1121  ++p;
1122  TEST_NOT_EQUAL(p, db.postlist_end("world"));
1123  TEST_EQUAL(*p, 3);
1124  ++p;
1125  TEST_EQUAL(p, db.postlist_end("world"));
1126 }
1127 
1128 // Test replacing a document with itself without modifying postings.
1129 // Regression test for bug in 0.9.9 and earlier - there flint and quartz
1130 // lost all positional information for the document when you did this.
1131 DEFINE_TESTCASE(replacedoc5, writable) {
1133 
1134  {
1135  Xapian::Document doc;
1136  doc.add_posting("hello", 1);
1137  doc.add_posting("world", 2);
1138 
1139  Xapian::docid did = db.add_document(doc);
1140  TEST_EQUAL(did, 1);
1141  db.commit();
1142  }
1143 
1144  {
1145  Xapian::Document doc = db.get_document(1);
1146  TEST(db.has_positions());
1147  TEST(db.positionlist_begin(1, "hello") != db.positionlist_end(1, "hello"));
1148  TEST(db.positionlist_begin(1, "world") != db.positionlist_end(1, "world"));
1149  db.replace_document(1, doc);
1150  db.commit();
1151 
1152  TEST(db.has_positions());
1153  TEST(db.positionlist_begin(1, "hello") != db.positionlist_end(1, "hello"));
1154  TEST(db.positionlist_begin(1, "world") != db.positionlist_end(1, "world"));
1155  }
1156 
1157  // The backends now spot simple cases of replacing the same document and
1158  // don't do needless work. Force them to actually do the replacement to
1159  // make sure that case works.
1160 
1161  {
1162  Xapian::Document doc;
1163  doc.add_term("Q2");
1164  db.add_document(doc);
1165  db.commit();
1166  }
1167 
1168  {
1169  Xapian::Document doc = db.get_document(1);
1170  TEST(db.has_positions());
1171  TEST(db.positionlist_begin(1, "hello") != db.positionlist_end(1, "hello"));
1172  TEST(db.positionlist_begin(1, "world") != db.positionlist_end(1, "world"));
1173  (void)db.get_document(2);
1174  db.replace_document(1, doc);
1175  db.commit();
1176 
1177  TEST(db.has_positions());
1178  TEST(db.positionlist_begin(1, "hello") != db.positionlist_end(1, "hello"));
1179  TEST(db.positionlist_begin(1, "world") != db.positionlist_end(1, "world"));
1180  }
1181 }
1182 
1183 // Test replacing a document while adding values, without changing anything
1184 // else. Regression test for a bug introduced while implementing lazy update,
1185 // and also covers a few other code paths.
1186 DEFINE_TESTCASE(replacedoc6, writable) {
1188 
1189  Xapian::Document doc;
1190  Xapian::docid did = db.add_document(doc);
1191  TEST_EQUAL(did, 1);
1192  db.commit();
1193 
1194  // Add document
1195  doc = db.get_document(1);
1196  TEST_EQUAL(doc.get_value(1), "");
1197  TEST_EQUAL(doc.get_value(2), "");
1198  doc.add_value(1, "banana1");
1199  db.replace_document(1, doc);
1200 
1201  doc = db.get_document(1);
1202  TEST_EQUAL(doc.get_value(1), "banana1");
1203  TEST_EQUAL(doc.get_value(2), "");
1204  db.commit();
1205 
1206  doc = db.get_document(1);
1207  TEST_EQUAL(doc.get_value(1), "banana1");
1208  TEST_EQUAL(doc.get_value(2), "");
1209  doc.add_value(2, "banana2");
1210  db.replace_document(1, doc);
1211 
1212  TEST_EQUAL(doc.get_value(1), "banana1");
1213  TEST_EQUAL(doc.get_value(2), "banana2");
1214  db.commit();
1215 
1216  doc = db.get_document(1);
1217  TEST_EQUAL(doc.get_value(1), "banana1");
1218  TEST_EQUAL(doc.get_value(2), "banana2");
1219 }
1220 
1221 // Test of new feature: WritableDatabase::replace_document and delete_document
1222 // can take a unique termname instead of a document id as of Xapian 0.8.2.
1223 DEFINE_TESTCASE(uniqueterm1, writable) {
1225 
1226  for (int n = 1; n <= 20; ++n) {
1227  Xapian::Document doc;
1228  string uterm = "U" + str(n % 16);
1229  doc.add_term(uterm);
1230  doc.add_term(str(n));
1231  doc.add_term(str(n ^ 9));
1232  doc.add_term("all");
1233  doc.set_data("pass1");
1234  db.add_document(doc);
1235  }
1236 
1237  TEST_EQUAL(db.get_doccount(), 20);
1238 
1239  static const Xapian::doccount sizes[20] = {
1240  19, 17, 16, 15,
1241  15, 15, 15, 15,
1242  15, 15, 15, 15,
1243  15, 15, 15, 15,
1244  15, 15, 15, 15
1245  };
1246  for (int n = 1; n <= 20; ++n) {
1247  string uterm = "U" + str(n % 16);
1248  if (uterm == "U2") {
1249  db.delete_document(uterm);
1250  } else {
1251  Xapian::Document doc;
1252  doc.add_term(uterm);
1253  doc.add_term(str(n));
1254  doc.add_term(str(n ^ 9));
1255  doc.add_term("all");
1256  doc.set_data("pass2");
1257  db.replace_document(uterm, doc);
1258  }
1259  TEST_EQUAL(db.get_doccount(), sizes[n - 1]);
1260  }
1261 
1262  string uterm = "U571";
1263  Xapian::Document doc;
1264  doc.add_term(uterm);
1265  doc.set_data("pass3");
1266  db.replace_document(uterm, doc);
1267 
1268  TEST_EQUAL(db.get_doccount(), 16);
1269 
1270  db.delete_document("U2");
1271 
1272  TEST_EQUAL(db.get_doccount(), 16);
1273 }
1274 
1275 // tests all document postlists
1276 DEFINE_TESTCASE(allpostlist2, writable) {
1277  Xapian::WritableDatabase db(get_writable_database("apitest_manydocs"));
1279  unsigned int j = 1;
1280  while (i != db.postlist_end("")) {
1281  TEST_EQUAL(*i, j);
1282  i++;
1283  j++;
1284  }
1285  TEST_EQUAL(j, 513);
1286 
1287  db.delete_document(1);
1288  db.delete_document(50);
1289  db.delete_document(512);
1290 
1291  i = db.postlist_begin("");
1292  j = 2;
1293  while (i != db.postlist_end("")) {
1294  TEST_EQUAL(*i, j);
1295  i++;
1296  j++;
1297  if (j == 50) j++;
1298  }
1299  TEST_EQUAL(j, 512);
1300 
1301  i = db.postlist_begin("");
1302  j = 2;
1303  while (i != db.postlist_end("")) {
1304  TEST_EQUAL(*i, j);
1305  i++;
1306  j++;
1307  if (j == 40) {
1308  j += 10;
1309  i.skip_to(j);
1310  j++;
1311  }
1312  }
1313  TEST_EQUAL(j, 512);
1314 }
1315 
1317 {
1318  // Don't bother with postlist_begin() because allpostlist tests cover that.
1320  TEST_EQUAL(db.get_doccount(), db.get_termfreq(""));
1321  TEST_EQUAL(db.get_doccount() != 0, db.term_exists(""));
1323 }
1324 
1325 // tests results of passing an empty term to various methods
1326 // equivalent of emptyterm1 for a writable database
1327 DEFINE_TESTCASE(emptyterm2, writable) {
1328  {
1329  Xapian::WritableDatabase db(get_writable_database("apitest_manydocs"));
1330  TEST_EQUAL(db.get_doccount(), 512);
1332  db.delete_document(1);
1333  TEST_EQUAL(db.get_doccount(), 511);
1335  db.delete_document(50);
1336  TEST_EQUAL(db.get_doccount(), 510);
1338  db.delete_document(512);
1339  TEST_EQUAL(db.get_doccount(), 509);
1341  }
1342 
1343  {
1344  Xapian::WritableDatabase db(get_writable_database("apitest_onedoc"));
1345  TEST_EQUAL(db.get_doccount(), 1);
1347  db.delete_document(1);
1348  TEST_EQUAL(db.get_doccount(), 0);
1350  }
1351 
1352  {
1354  TEST_EQUAL(db.get_doccount(), 0);
1356  }
1357 }
1358 
1359 // Check that PHRASE/NEAR becomes AND if there's no positional info in the
1360 // database.
1361 DEFINE_TESTCASE(phraseorneartoand1, backend) {
1362  Xapian::Database db = get_database("phraseorneartoand1",
1363  [](Xapian::WritableDatabase& wdb,
1364  const string&)
1365  {
1366  for (int n = 1; n <= 20; ++n) {
1367  Xapian::Document doc;
1368  doc.add_term(str(n));
1369  doc.add_term(str(n ^ 9));
1370  doc.add_term("all");
1371  doc.set_data("pass1");
1372  wdb.add_document(doc);
1373  }
1374  });
1375 
1376  Xapian::Enquire enquire(db);
1377  Xapian::MSet mymset;
1378 
1379  static const char * const q1[] = { "all", "1" };
1380  enquire.set_query(Xapian::Query(Xapian::Query::OP_PHRASE, q1, q1 + 2));
1381  mymset = enquire.get_mset(0, 10);
1382  TEST_EQUAL(2, mymset.size());
1383 
1384  enquire.set_query(Xapian::Query(Xapian::Query::OP_NEAR, q1, q1 + 2));
1385  mymset = enquire.get_mset(0, 10);
1386  TEST_EQUAL(2, mymset.size());
1387 
1388  static const char * const q2[] = { "1", "2" };
1389  enquire.set_query(Xapian::Query(Xapian::Query::OP_PHRASE, q2, q2 + 2));
1390  mymset = enquire.get_mset(0, 10);
1391  TEST_EQUAL(0, mymset.size());
1392 
1393  enquire.set_query(Xapian::Query(Xapian::Query::OP_NEAR, q2, q2 + 2));
1394  mymset = enquire.get_mset(0, 10);
1395  TEST_EQUAL(0, mymset.size());
1396 }
1397 
1398 static void
1400 {
1401  Xapian::Document doc;
1402  for (Xapian::termpos n = 1; n <= 2000; ++n) {
1403  doc.add_posting("fork", n * 3);
1404  doc.add_posting("knife", n * unsigned(log(double(n + 2))));
1405  doc.add_posting("spoon", n * n);
1406  // Exercise positions up to 4 billion.
1407  Xapian::termpos half_cube = n * n / 2 * n;
1408  doc.add_posting("chopsticks", half_cube);
1409  if (sizeof(Xapian::termpos) >= 8) {
1410  // Exercise 64-bit positions.
1411  doc.add_posting("spork", half_cube * half_cube);
1412  }
1413  }
1414  doc.set_data("cutlery");
1415  db.add_document(doc);
1416 }
1417 
1418 // Check that a large number of position list entries for a particular term
1419 // works - regression test for flint.
1420 DEFINE_TESTCASE(longpositionlist1, backend) {
1421  Xapian::Database db = get_database("longpositionlist1",
1423 
1424  Xapian::Document doc = db.get_document(1);
1425 
1426  Xapian::TermIterator t, tend;
1427  Xapian::PositionIterator p, pend;
1428 
1429  t = doc.termlist_begin();
1430  tend = doc.termlist_end();
1431 
1432  TEST(t != tend);
1433  TEST_EQUAL(*t, "chopsticks");
1434  p = t.positionlist_begin();
1435  pend = t.positionlist_end();
1436  for (Xapian::termpos n = 1; n <= 2000; ++n) {
1437  TEST(p != pend);
1438  Xapian::termpos half_cube = n * n / 2 * n;
1439  TEST_EQUAL(*p, half_cube);
1440  ++p;
1441  }
1442  TEST(p == pend);
1443 
1444  ++t;
1445  TEST(t != tend);
1446  TEST_EQUAL(*t, "fork");
1447  p = t.positionlist_begin();
1448  pend = t.positionlist_end();
1449  for (Xapian::termpos n = 1; n <= 2000; ++n) {
1450  TEST(p != pend);
1451  TEST_EQUAL(*p, n * 3);
1452  ++p;
1453  }
1454  TEST(p == pend);
1455 
1456  ++t;
1457  TEST(t != tend);
1458  TEST_EQUAL(*t, "knife");
1459  p = t.positionlist_begin();
1460  pend = t.positionlist_end();
1461  for (Xapian::termpos n = 1; n <= 2000; ++n) {
1462  TEST(p != pend);
1463  TEST_EQUAL(*p, n * unsigned(log(double(n + 2))));
1464  ++p;
1465  }
1466  TEST(p == pend);
1467 
1468  ++t;
1469  TEST(t != tend);
1470  TEST_EQUAL(*t, "spoon");
1471  p = t.positionlist_begin();
1472  pend = t.positionlist_end();
1473  for (Xapian::termpos n = 1; n <= 2000; ++n) {
1474  TEST(p != pend);
1475  TEST_EQUAL(*p, n * n);
1476  ++p;
1477  }
1478  TEST(p == pend);
1479 
1480  if (sizeof(Xapian::termpos) >= 8) {
1481  ++t;
1482  TEST(t != tend);
1483  TEST_EQUAL(*t, "spork");
1484  p = t.positionlist_begin();
1485  pend = t.positionlist_end();
1486  for (Xapian::termpos n = 1; n <= 2000; ++n) {
1487  TEST(p != pend);
1488  Xapian::termpos half_cube = n * n / 2 * n;
1489  TEST_EQUAL(*p, half_cube * half_cube);
1490  ++p;
1491  }
1492  TEST(p == pend);
1493  }
1494 
1495  ++t;
1496  TEST(t == tend);
1497 }
1498 
1499 static void
1501 {
1502  char buf[2] = "X";
1503 
1504  // Add 5 documents indexed by "test" with wdf 1.
1505  for (int i = 0; i < 5; ++i) {
1506  Xapian::Document doc;
1507  *buf = '0' + i;
1508  doc.add_value(0, buf);
1509  doc.add_term("test");
1510  db.add_document(doc);
1511  }
1512 
1513  // Add 5 documents indexed by "test" with wdf 2.
1514  for (int i = 0; i < 5; ++i) {
1515  Xapian::Document doc;
1516  *buf = '0' + i;
1517  doc.add_value(0, buf);
1518  doc.add_term("test", 2);
1519  db.add_document(doc);
1520  }
1521 }
1522 
1523 // Regression test for bug#110: Inconsistent sort order between pages with
1524 // set_sort_by_value_then_relevance.
1525 DEFINE_TESTCASE(consistency2, backend) {
1526  Xapian::Database db = get_database("consistency2", gen_consistency2_db);
1527 
1528  Xapian::Enquire enq(db);
1529  enq.set_query(Xapian::Query("test"));
1530 
1531  enq.set_sort_by_value_then_relevance(0, true);
1532 
1533  // 10 results, unpaged.
1534  Xapian::MSet mset1 = enq.get_mset(0, 10);
1535  TEST_EQUAL(mset1.size(), 10);
1536 
1537  // 10 results, split.
1538  Xapian::MSet mset2a = enq.get_mset(0, 1);
1539  TEST_EQUAL(mset2a.size(), 1);
1540  Xapian::MSet mset2b = enq.get_mset(1, 1);
1541  TEST_EQUAL(mset2b.size(), 1);
1542  Xapian::MSet mset2c = enq.get_mset(2, 8);
1543  TEST_EQUAL(mset2c.size(), 8);
1544 
1545  TEST_EQUAL(*mset1[0], *mset2a[0]);
1546  TEST_EQUAL(*mset1[1], *mset2b[0]);
1547  for (Xapian::doccount i = 0; i < 8; ++i) {
1548  TEST_EQUAL(*mset1[i + 2], *mset2c[i]);
1549  }
1550 }
1551 
1552 DEFINE_TESTCASE(crashrecovery1, chert) {
1553  // Glass has a single version file per revision, rather than multiple base
1554  // files, so it simply can't get into the situations we are testing
1555  // recovery from.
1556  const string & dbtype = get_dbtype();
1557  string path = ".";
1558  path += dbtype;
1559  path += "/dbw";
1560  const char * base_ext = ".baseB";
1561 
1562  Xapian::Document doc;
1563  {
1566  TEST_EQUAL(dbr.get_doccount(), 0);
1567 
1568  // Xapian::Database has full set of baseA, no baseB
1569  TEST(file_exists(path + "/postlist.baseA"));
1570  TEST(file_exists(path + "/record.baseA"));
1571  TEST(file_exists(path + "/termlist.baseA"));
1572  TEST(!file_exists(path + "/postlist.baseB"));
1573  TEST(!file_exists(path + "/record.baseB"));
1574  TEST(!file_exists(path + "/termlist.baseB"));
1575 
1576  db.add_document(doc);
1577  db.commit();
1578  TEST(dbr.reopen());
1579  TEST_EQUAL(dbr.get_doccount(), 1);
1580 
1581  // Xapian::Database has full set of baseB, old baseA
1582  TEST(file_exists(path + "/postlist.baseA"));
1583  TEST(file_exists(path + "/record.baseA"));
1584  TEST(file_exists(path + "/termlist.baseA"));
1585  TEST(file_exists(path + "/postlist.baseB"));
1586  TEST(file_exists(path + "/record.baseB"));
1587  TEST(file_exists(path + "/termlist.baseB"));
1588 
1589  db.add_document(doc);
1590  db.commit();
1591  TEST(dbr.reopen());
1592  TEST_EQUAL(dbr.get_doccount(), 2);
1593 
1594  // Xapian::Database has full set of baseA, old baseB
1595  TEST(file_exists(path + "/postlist.baseA"));
1596  TEST(file_exists(path + "/record.baseA"));
1597  TEST(file_exists(path + "/termlist.baseA"));
1598  TEST(file_exists(path + "/postlist.baseB"));
1599  TEST(file_exists(path + "/record.baseB"));
1600  TEST(file_exists(path + "/termlist.baseB"));
1601 
1602  // Simulate a transaction starting, some of the baseB getting removed,
1603  // but then the transaction fails.
1604  unlink((path + "/record" + base_ext).c_str());
1605  unlink((path + "/termlist" + base_ext).c_str());
1606 
1607  TEST(!dbr.reopen());
1608  TEST_EQUAL(dbr.get_doccount(), 2);
1609  }
1610 
1612  // Xapian::Database has full set of baseA, some old baseB
1613  TEST(file_exists(path + "/postlist.baseA"));
1614  TEST(file_exists(path + "/record.baseA"));
1615  TEST(file_exists(path + "/termlist.baseA"));
1616  TEST(file_exists(path + "/postlist.baseB"));
1617  TEST(!file_exists(path + "/record.baseB"));
1618  TEST(!file_exists(path + "/termlist.baseB"));
1619  Xapian::Database dbr = Xapian::Database(path);
1620 
1621  db.add_document(doc);
1622  db.commit();
1623  TEST(dbr.reopen());
1624  TEST_EQUAL(dbr.get_doccount(), 3);
1625 
1626  // Xapian::Database has full set of baseB, old baseA
1627  TEST(file_exists(path + "/postlist.baseA"));
1628  TEST(file_exists(path + "/record.baseA"));
1629  TEST(file_exists(path + "/termlist.baseA"));
1630  TEST(file_exists(path + "/postlist.baseB"));
1631  TEST(file_exists(path + "/record.baseB"));
1632  TEST(file_exists(path + "/termlist.baseB"));
1633 
1634  db.add_document(doc);
1635  db.commit();
1636  TEST(dbr.reopen());
1637  TEST_EQUAL(dbr.get_doccount(), 4);
1638 }
1639 
1640 // Check that DatabaseError is thrown if the docid counter would wrap.
1641 // Regression test for bug#152.
1642 DEFINE_TESTCASE(nomoredocids1, writable) {
1643  // The InMemory backend uses a vector for the documents, so trying to add
1644  // a document with a really large docid will fail because we can't allocate
1645  // enough memory!
1646  SKIP_TEST_FOR_BACKEND("inmemory");
1647 
1649  Xapian::Document doc;
1650  doc.set_data("prose");
1651  doc.add_term("word");
1652 
1653  Xapian::docid max_id = numeric_limits<Xapian::docid>::max();
1654  db.replace_document(max_id, doc);
1655 
1657 
1658  // Also test replace_document() by term which will try to add a new
1659  // document if the term isn't present - that should also fail if the
1660  // docid counter would wrap.
1662 }
1663 
1664 // Test synonym iterators.
1665 DEFINE_TESTCASE(synonymitor1, writable && synonyms) {
1667 
1668  // Test iterators for terms which aren't there.
1669  TEST(db.synonyms_begin("abc") == db.synonyms_end("abc"));
1670 
1671  // Test iterating the synonym keys when there aren't any.
1673 
1674  db.add_synonym("hello", "howdy");
1675  db.add_synonym("hello", "hi");
1676  db.add_synonym("goodbye", "bye");
1677  db.add_synonym("goodbye", "farewell");
1678 
1680  string s;
1681 
1682  // Try these tests twice - once before committing and once after.
1683  for (int times = 1; times <= 2; ++times) {
1684  // Test iterators for terms which aren't there.
1685  TEST(db.synonyms_begin("abc") == db.synonyms_end("abc"));
1686  TEST(db.synonyms_begin("ghi") == db.synonyms_end("ghi"));
1687  TEST(db.synonyms_begin("zzzzz") == db.synonyms_end("zzzzz"));
1688 
1689  s = "|";
1690  t = db.synonyms_begin("hello");
1691  while (t != db.synonyms_end("hello")) {
1692  s += *t++;
1693  s += '|';
1694  }
1695  TEST_STRINGS_EQUAL(s, "|hi|howdy|");
1696 
1697  s = "|";
1698  t = db.synonyms_begin("goodbye");
1699  while (t != db.synonyms_end("goodbye")) {
1700  s += *t++;
1701  s += '|';
1702  }
1703  TEST_STRINGS_EQUAL(s, "|bye|farewell|");
1704 
1705  s = "|";
1706  t = db.synonym_keys_begin();
1707  while (t != db.synonym_keys_end()) {
1708  s += *t++;
1709  s += '|';
1710  }
1711  TEST_STRINGS_EQUAL(s, "|goodbye|hello|");
1712 
1713  db.commit();
1714  }
1715 
1716  // Delete a synonym for "hello" and all synonyms for "goodbye".
1717  db.remove_synonym("hello", "hi");
1718  db.clear_synonyms("goodbye");
1719 
1720  // Try these tests twice - once before committing and once after.
1721  for (int times = 1; times <= 2; ++times) {
1722  // Test iterators for terms which aren't there.
1723  TEST(db.synonyms_begin("abc") == db.synonyms_end("abc"));
1724  TEST(db.synonyms_begin("ghi") == db.synonyms_end("ghi"));
1725  TEST(db.synonyms_begin("zzzzz") == db.synonyms_end("zzzzz"));
1726 
1727  s = "|";
1728  t = db.synonyms_begin("hello");
1729  while (t != db.synonyms_end("hello")) {
1730  s += *t++;
1731  s += '|';
1732  }
1733  TEST_STRINGS_EQUAL(s, "|howdy|");
1734 
1735  TEST(db.synonyms_begin("goodbye") == db.synonyms_end("goodbye"));
1736 
1737  s = "|";
1738  t = db.synonym_keys_begin();
1739  while (t != db.synonym_keys_end()) {
1740  s += *t++;
1741  s += '|';
1742  }
1743  TEST_STRINGS_EQUAL(s, "|hello|");
1744 
1745  db.commit();
1746  }
1747 
1748  Xapian::Database db_multi;
1749  db_multi.add_database(db);
1750  db_multi.add_database(get_database("apitest_simpledata"));
1751 
1752  // Test iterators for terms which aren't there.
1753  TEST(db_multi.synonyms_begin("abc") == db_multi.synonyms_end("abc"));
1754  TEST(db_multi.synonyms_begin("ghi") == db_multi.synonyms_end("ghi"));
1755  TEST(db_multi.synonyms_begin("zzzzz") == db_multi.synonyms_end("zzzzz"));
1756 
1757  s = "|";
1758  t = db_multi.synonyms_begin("hello");
1759  while (t != db_multi.synonyms_end("hello")) {
1760  s += *t++;
1761  s += '|';
1762  }
1763  TEST_STRINGS_EQUAL(s, "|howdy|");
1764 
1765  TEST(db_multi.synonyms_begin("goodbye") == db_multi.synonyms_end("goodbye"));
1766 
1767  s = "|";
1768  t = db_multi.synonym_keys_begin();
1769  while (t != db_multi.synonym_keys_end()) {
1770  s += *t++;
1771  s += '|';
1772  }
1773  TEST_STRINGS_EQUAL(s, "|hello|");
1774 }
1775 
1776 // Test that adding a document with a really long term gives an error on
1777 // add_document() rather than on commit().
1778 DEFINE_TESTCASE(termtoolong1, writable) {
1779  // Inmemory doesn't impose a limit.
1780  SKIP_TEST_FOR_BACKEND("inmemory");
1781 
1783 
1784  for (size_t i = 246; i <= 290; ++i) {
1785  tout.str(string());
1786  tout << "Term length " << i << '\n';
1787  Xapian::Document doc;
1788  string term(i, 'X');
1789  doc.add_term(term);
1790  try {
1791  db.add_document(doc);
1792  TEST_AND_EXPLAIN(false, "Expecting exception InvalidArgumentError");
1793  } catch (const Xapian::InvalidArgumentError &e) {
1794  // Check that the max length is correctly expressed in the
1795  // exception message - we've got this wrong in two different ways
1796  // in the past!
1797  tout << e.get_msg() << '\n';
1798  TEST(e.get_msg().find("Term too long (> 245)") != string::npos);
1799  }
1800  }
1801 
1802  for (size_t j = 240; j <= 245; ++j) {
1803  tout.str(string());
1804  tout << "Term length " << j << '\n';
1805  Xapian::Document doc;
1806  string term(j, 'X');
1807  doc.add_term(term);
1808  db.add_document(doc);
1809  }
1810 
1811  db.commit();
1812 
1813  size_t limit = endswith(get_dbtype(), "glass") ? 255 : 252;
1814  {
1815  // Currently chert and glass escape zero bytes from terms in keys for
1816  // some tables, so a term with 127 zero bytes won't work for chert, and
1817  // with 128 zero bytes won't work for glass.
1818  Xapian::Document doc;
1819  doc.add_term(string(limit / 2 + 1, '\0'));
1820  db.add_document(doc);
1821  try {
1822  db.commit();
1823  TEST_AND_EXPLAIN(false, "Expecting exception InvalidArgumentError");
1824  } catch (const Xapian::InvalidArgumentError &e) {
1825  // Check that the max length is correctly expressed in the
1826  // exception message - we've got this wrong in two different ways
1827  // in the past!
1828  tout << e.get_msg() << '\n';
1829  string target = " is ";
1830  target += str(limit);
1831  target += " bytes";
1832  TEST(e.get_msg().find(target) != string::npos);
1833  }
1834  }
1835 
1836  if (get_dbtype().find("chert") != string::npos) {
1837  XFAIL("Chert fails to clear pending changes after "
1838  "InvalidArgumentError - fix too invasive");
1839  }
1840  // Try adding a document. Regression test for a bug fixed in 1.4.15 - in
1841  // earlier versions the pending changes which caused the
1842  // InvalidArgumentError were left around and a subsequent commit() on the
1843  // same WritableDatabase would also fail with InvalidArgumentError.
1844  Xapian::Document doc;
1845  doc.add_term("ok");
1846  db.add_document(doc);
1847  db.commit();
1848 }
1849 
1851 DEFINE_TESTCASE(postlist7, writable) {
1853 
1854  {
1855  Xapian::Document doc;
1856  doc.add_term("foo", 3);
1857  doc.add_term("zz", 4);
1858  db_w.replace_document(5, doc);
1859  }
1860 
1862  p = db_w.postlist_begin("foo");
1863  TEST(p != db_w.postlist_end("foo"));
1864  TEST_EQUAL(*p, 5);
1865  TEST_EQUAL(p.get_wdf(), 3);
1866  TEST_EQUAL(p.get_doclength(), 7);
1867  TEST_EQUAL(p.get_unique_terms(), 2);
1868  ++p;
1869  TEST(p == db_w.postlist_end("foo"));
1870 
1871  {
1872  Xapian::Document doc;
1873  doc.add_term("foo", 1);
1874  doc.add_term("zz", 1);
1875  db_w.replace_document(6, doc);
1876  }
1877 
1878  p = db_w.postlist_begin("foo");
1879  TEST(p != db_w.postlist_end("foo"));
1880  TEST_EQUAL(*p, 5);
1881  TEST_EQUAL(p.get_wdf(), 3);
1882  TEST_EQUAL(p.get_doclength(), 7);
1883  TEST_EQUAL(p.get_unique_terms(), 2);
1884  ++p;
1885  TEST(p != db_w.postlist_end("foo"));
1886  TEST_EQUAL(*p, 6);
1887  TEST_EQUAL(p.get_wdf(), 1);
1888  TEST_EQUAL(p.get_doclength(), 2);
1889  TEST_EQUAL(p.get_unique_terms(), 2);
1890  ++p;
1891  TEST(p == db_w.postlist_end("foo"));
1892 }
1893 
1894 static void
1896 {
1897  Xapian::Document doc;
1898  doc.add_term("foo");
1899  db.add_document(doc);
1900  db.commit();
1901 
1902  string synonym(255, 'x');
1903  char buf[] = " iamafish!!!!!!!!!!";
1904  for (int i = 33; i < 120; ++i) {
1905  db.add_synonym(buf, synonym);
1906  ++buf[0];
1907  }
1908 }
1909 
1910 DEFINE_TESTCASE(lazytablebug1, synonyms) {
1911  Xapian::Database db = get_database("lazytablebug1", gen_lazytablebug1_db);
1912  for (Xapian::TermIterator t = db.synonym_keys_begin(); t != db.synonym_keys_end(); ++t) {
1913  tout << *t << '\n';
1914  }
1915 }
1916 
1922 DEFINE_TESTCASE(cursordelbug1, writable && path) {
1923  static const int terms[] = { 219, 221, 222, 223, 224, 225, 226 };
1924  static const int copies[] = { 74, 116, 199, 21, 45, 155, 189 };
1925 
1927  db = get_named_writable_database("cursordelbug1", string());
1928 
1929  for (size_t i = 0; i < sizeof(terms) / sizeof(terms[0]); ++i) {
1930  Xapian::Document doc;
1931  doc.add_term("XC" + str(terms[i]));
1932  doc.add_term("XTabc");
1933  doc.add_term("XAdef");
1934  doc.add_term("XRghi");
1935  doc.add_term("XYabc");
1936  size_t c = copies[i];
1937  while (c--) db.add_document(doc);
1938  }
1939 
1940  db.commit();
1941 
1942  for (size_t i = 0; i < sizeof(terms) / sizeof(terms[0]); ++i) {
1943  db.delete_document("XC" + str(terms[i]));
1944  }
1945 
1946  db.commit();
1947 
1948  const string & db_path = get_named_writable_database_path("cursordelbug1");
1949  TEST_EQUAL(Xapian::Database::check(db_path), 0);
1950 }
1951 
1955 static void
1956 check_vals(const Xapian::Database & db, const map<Xapian::docid, string> & vals)
1957 {
1958  TEST_EQUAL(db.get_doccount(), vals.size());
1959  if (vals.empty()) return;
1960  TEST_REL(vals.rbegin()->first,<=,db.get_lastdocid());
1961  map<Xapian::docid, string>::const_iterator i;
1962  for (i = vals.begin(); i != vals.end(); ++i) {
1963  tout.str(string());
1964  tout << "Checking value in doc " << i->first << " - should be '" << i->second << "'\n";
1965  Xapian::Document doc = db.get_document(i->first);
1966  string dbval = doc.get_value(1);
1967  TEST_EQUAL(dbval, i->second);
1968  if (dbval.empty()) {
1969  TEST_EQUAL(0, doc.values_count());
1970  TEST_EQUAL(doc.values_begin(), doc.values_end());
1971  } else {
1972  TEST_EQUAL(1, doc.values_count());
1973  Xapian::ValueIterator valit = doc.values_begin();
1974  TEST_NOT_EQUAL(valit, doc.values_end());
1975  TEST_EQUAL(dbval, *valit);
1976  TEST_EQUAL(1, valit.get_valueno());
1977  ++valit;
1978  TEST_EQUAL(valit, doc.values_end());
1979  }
1980  }
1981 }
1982 
1986 DEFINE_TESTCASE(modifyvalues1, writable) {
1987  unsigned int seed = 7;
1989  // Note: doccount must be coprime with 13
1990  const Xapian::doccount doccount = 1000;
1991  static_assert(doccount % 13 != 0, "doccount divisible by 13");
1992 
1993  map<Xapian::docid, string> vals;
1994 
1995  for (Xapian::doccount num = 1; num <= doccount; ++num) {
1996  tout.str(string());
1997  Xapian::Document doc;
1998  string val = "val" + str(num);
1999  tout << "Setting val '" << val << "' in doc " << num << "\n";
2000  doc.add_value(1, val);
2001  db.add_document(doc);
2002  vals[num] = val;
2003  }
2004  check_vals(db, vals);
2005  db.commit();
2006  check_vals(db, vals);
2007 
2008  // Modify one of the values (this is a regression test which failed with
2009  // the initial implementation of streaming values).
2010  {
2011  Xapian::Document doc;
2012  string val = "newval0";
2013  tout << "Setting val '" << val << "' in doc 2\n";
2014  doc.add_value(1, val);
2015  db.replace_document(2, doc);
2016  vals[2] = val;
2017  check_vals(db, vals);
2018  db.commit();
2019  check_vals(db, vals);
2020  }
2021 
2022  // Check that value doesn't get lost when replacing a document with itself.
2023  {
2024  tout << "Replacing document 1 with itself\n";
2025  Xapian::Document doc = db.get_document(1);
2026  db.replace_document(1, doc);
2027  check_vals(db, vals);
2028  db.commit();
2029  check_vals(db, vals);
2030  }
2031 
2032  // Check that value doesn't get lost when replacing a document with itself,
2033  // accessing another document in the meantime. This is a regression test
2034  // for a bug in the code which implements lazy updates - this used to
2035  // forget the values in the document in this situation.
2036  {
2037  tout << "Replacing document 1 with itself, after reading doc 2.\n";
2038  Xapian::Document doc = db.get_document(1);
2039  db.get_document(2);
2040  db.replace_document(1, doc);
2041  check_vals(db, vals);
2042  db.commit();
2043  check_vals(db, vals);
2044  }
2045 
2046  // Do some random modifications: seed random generator, for repeatable
2047  // results.
2048  tout << "Setting seed to " << seed << "\n";
2049  srand(seed);
2050  for (Xapian::doccount num = 1; num <= doccount * 2; ++num) {
2051  tout.str(string());
2052  Xapian::docid did = ((rand() >> 8) % doccount) + 1;
2053  Xapian::Document doc;
2054  string val;
2055 
2056  if (num % 5 != 0) {
2057  val = "newval" + str(num);
2058  tout << "Setting val '" << val << "' in doc " << did << "\n";
2059  doc.add_value(1, val);
2060  } else {
2061  tout << "Adding/replacing empty document " << did << "\n";
2062  }
2063  db.replace_document(did, doc);
2064  vals[did] = val;
2065  }
2066  check_vals(db, vals);
2067  db.commit();
2068  check_vals(db, vals);
2069 
2070  // Delete all the remaining values, in a slightly shuffled order.
2071  // This is where it's important that doccount is coprime with 13.
2072  for (Xapian::doccount num = 0; num < doccount * 13; num += 13) {
2073  tout.str(string());
2074  Xapian::docid did = (num % doccount) + 1;
2075  tout << "Clearing val in doc " << did << "\n";
2076  Xapian::Document doc;
2077  db.replace_document(did, doc);
2078  vals[did] = string();
2079  }
2080  check_vals(db, vals);
2081  db.commit();
2082  check_vals(db, vals);
2083 }
2084 
2098 DEFINE_TESTCASE(protocolbug1, remote && writable) {
2100  Xapian::Document doc;
2101  doc.add_term(string(300, 'x'));
2102 
2104  db.replace_document(1, doc));
2105  db.commit();
2106 }
2107 
2108 DEFINE_TESTCASE(remotefdleak1, remote && writable) {
2111  auto wdb2 = get_writable_database_again());
2112 }
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
bool endswith(const std::string &s, char sfx)
Definition: stringutils.h:75
Xapian::docid add_document(const Xapian::Document &document)
Add a new document to the database.
Definition: omdatabase.cc:902
static void gen_consistency2_db(Xapian::WritableDatabase &db, const string &)
Definition: api_wrdb.cc:1500
PositionIterator positionlist_end(Xapian::docid, const std::string &) const
Corresponding end iterator to positionlist_begin().
Definition: database.h:254
DEFINE_TESTCASE(adddoc1, writable)
Definition: api_wrdb.cc:57
void add_value(Xapian::valueno slot, const std::string &value)
Add a new value.
Definition: omdocument.cc:107
void set_sort_by_value_then_relevance(Xapian::valueno sort_key, bool reverse)
Set the sorting to be by value, then by relevance for documents with the same value.
Definition: omenquire.cc:884
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
#define TEST(a)
Test a condition, without an additional explanation for failure.
Definition: testsuite.h:275
This class is used to access a database, or a group of databases.
Definition: database.h:68
Xapian::termcount get_wdf() const
Return the wdf for the document at the current position.
InvalidOperationError indicates the API was used in an invalid way.
Definition: error.h:283
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
#define TEST_AND_EXPLAIN(a, b)
Test a condition, and display the test with an extra explanation if the condition fails...
Definition: testsuite.h:267
bool has_positions() const
Does this database have any positional information?
Definition: omdatabase.cc:238
ValueIterator values_begin() const
Iterator for the values in this document.
Definition: omdocument.cc:210
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
Xapian::docid get_lastdocid() const
Get the highest document id which has been used in the database.
Definition: omdatabase.cc:279
const std::string & get_msg() const
Message giving details of the error, intended for human consumption.
Definition: error.h:122
a generic test suite engine
Xapian::doccount get_termfreq() const
Return the term frequency for the term at the current position.
bool reopen()
Re-open the database.
Definition: omdatabase.cc:125
C++ function versions of useful Unix commands.
Class for iterating over document values.
Definition: valueiterator.h:40
void remove_term(const std::string &tname)
Remove a term and all postings associated with it.
Definition: omdocument.cc:177
Class representing a list of search results.
Definition: mset.h:44
Xapian::WritableDatabase get_writable_database_again()
Definition: apitest.cc:125
STL namespace.
Xapian::TermIterator synonyms_end(const std::string &) const
Corresponding end iterator to synonyms_begin(term).
Definition: database.h:447
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.
Utility functions for testing files.
void replace_document(Xapian::docid did, const Xapian::Document &document)
Replace a given document in the database.
Definition: omdatabase.cc:952
TermIterator termlist_end() const
Equivalent end iterator for termlist_begin().
Definition: document.h:270
Xapian::doccount get_doccount() const
Get the number of documents in the database.
Definition: omdatabase.cc:267
static void gen_longpositionlist1_db(Xapian::WritableDatabase &db, const string &)
Definition: api_wrdb.cc:1399
Xapian::WritableDatabase get_named_writable_database(const std::string &name, const std::string &source)
Definition: apitest.cc:93
test functionality of the Xapian API
static void test_emptyterm2_helper(Xapian::WritableDatabase &db)
Definition: api_wrdb.cc:1316
Xapian::doclength get_avlength() const
Get the average length of the documents in the database.
Definition: omdatabase.cc:293
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.
PositionIterator positionlist_end() const
Return an end PositionIterator for the current term.
Definition: termiterator.h:110
#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
void remove_synonym(const std::string &term, const std::string &synonym) const
Remove a synonym for a term.
Definition: omdatabase.cc:1039
std::string get_named_writable_database_path(const std::string &name)
Definition: apitest.cc:99
DatabaseLockError indicates failure to lock a database.
Definition: error.h:493
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
std::ostringstream tout
The debug printing stream.
Definition: testsuite.cc:104
Match only documents where all subqueries match near and in order.
Definition: query.h:152
Public interfaces for the Xapian library.
static void gen_lazytablebug1_db(Xapian::WritableDatabase &db, const string &)
Definition: api_wrdb.cc:1895
void XFAIL(const char *msg)
Mark a testcase as expected to fail.
Definition: testsuite.h:332
Xapian::TermIterator synonym_keys_begin(const std::string &prefix=std::string()) const
An iterator which returns all terms which have synonyms.
Definition: omdatabase.cc:740
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
Xapian::TermIterator synonym_keys_end(const std::string &=std::string()) const
Corresponding end iterator to synonym_keys_begin(prefix).
Definition: database.h:459
std::string get_dbtype()
Definition: apitest.cc:42
string str(int value)
Convert int to std::string.
Definition: str.cc:90
static int seed
Definition: stemtest.cc:45
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::termcount values_count() const
Count the values in this document.
Definition: omdocument.cc:204
Xapian::Database get_writable_database_as_database()
Definition: apitest.cc:119
Class for iterating over term positions.
Xapian::termcount get_wdf() const
Return the wdf for the term at the current position.
ValueIterator values_end() const
Equivalent end iterator for values_begin().
Definition: document.h:281
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
Xapian::TermIterator synonyms_begin(const std::string &term) const
An iterator which returns all the synonyms for a given term.
Definition: omdatabase.cc:722
#define SKIP_TEST_FOR_BACKEND(B)
Definition: apitest.h:75
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
bool term_exists(const std::string &tname) const
Check if a given term exists in the database.
Definition: omdatabase.cc:524
void clear_synonyms(const std::string &term) const
Remove all synonyms for a term.
Definition: omdatabase.cc:1052
static void check_vals(const Xapian::Database &db, const map< Xapian::docid, string > &vals)
Helper function for modifyvalues1.
Definition: api_wrdb.cc:1956
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 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
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
Xapian::termcount get_doclength() const
Return the length of the document at the current position.
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
Various handy helpers which std::string really should provide.
#define TEST_STRINGS_EQUAL(a, b)
Test for equality of two strings.
Definition: testsuite.h:287
<unistd.h>, but with compat.
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
Various assertion macros.
void add_synonym(const std::string &term, const std::string &synonym) const
Add a synonym for a term.
Definition: omdatabase.cc:1028
unsigned XAPIAN_DOCID_BASE_TYPE docid
A unique identifier for a document.
Definition: types.h:52
Class representing a query.
Definition: query.h:46
DatabaseError indicates some sort of database related error.
Definition: error.h:367
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
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
std::string get_value(Xapian::valueno slot) const
Get value by number.
Definition: omdocument.cc:64
TermIterator termlist_begin() const
Start iterating the terms in this document.
Definition: omdocument.cc:197
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
PositionIterator positionlist_begin() const
Return a PositionIterator for the current term.
A handle representing a document in a Xapian database.
Definition: document.h:61
void remove_posting(const std::string &tname, Xapian::termpos tpos, Xapian::termcount wdfdec=1)
Remove a posting of a term from the document.
Definition: omdocument.cc:150
Xapian::termcount get_unique_terms() const
Return the number of unique terms in the current document.
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 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