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