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