2 * @brief tests of posting sources
4 /* Copyright 2008,2009,2011,2015,2016,2019 Olly Betts
5 * Copyright 2008,2009 Lemur Consulting Ltd
6 * Copyright 2010 Richard Boulton
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
26 #include "api_postingsource.h"
31 #include "safeunistd.h"
34 #include "testutils.h"
39 class MyOddPostingSource
: public Xapian::PostingSource
{
40 Xapian::doccount num_docs
;
42 Xapian::doccount last_docid
;
46 MyOddPostingSource(Xapian::doccount num_docs_
,
47 Xapian::doccount last_docid_
)
48 : num_docs(num_docs_
), last_docid(last_docid_
), did(0)
52 MyOddPostingSource(const Xapian::Database
&db
)
53 : num_docs(db
.get_doccount()), last_docid(db
.get_lastdocid()), did(0)
56 PostingSource
* clone() const { return new MyOddPostingSource(num_docs
, last_docid
); }
58 void init(const Xapian::Database
&) { did
= 0; }
60 // These bounds could be better, but that's not important here.
61 Xapian::doccount
get_termfreq_min() const { return 0; }
63 Xapian::doccount
get_termfreq_est() const { return num_docs
/ 2; }
65 Xapian::doccount
get_termfreq_max() const { return num_docs
; }
67 void next(double wt
) {
70 if (did
% 2 == 0) ++did
;
73 void skip_to(Xapian::docid to_did
, double wt
) {
76 if (did
% 2 == 0) ++did
;
80 // Doesn't work if last_docid is 2^32 - 1.
81 return did
> last_docid
;
84 Xapian::docid
get_docid() const { return did
; }
86 string
get_description() const { return "MyOddPostingSource"; }
89 DEFINE_TESTCASE(externalsource1
, backend
&& !remote
&& !multi
) {
90 // Doesn't work for remote without registering with the server.
91 // Doesn't work for multi because it checks the docid in the
93 Xapian::Database
db(get_database("apitest_phrase"));
94 Xapian::Enquire
enq(db
);
95 MyOddPostingSource
src(db
);
97 // Check that passing NULL is rejected as intended.
98 Xapian::PostingSource
* nullsrc
= NULL
;
99 TEST_EXCEPTION(Xapian::InvalidArgumentError
, Xapian::Query
bad(nullsrc
));
101 enq
.set_query(Xapian::Query(&src
));
103 Xapian::MSet mset
= enq
.get_mset(0, 10);
104 mset_expect_order(mset
, 1, 3, 5, 7, 9, 11, 13, 15, 17);
106 Xapian::Query
q(Xapian::Query::OP_FILTER
,
107 Xapian::Query("leav"),
108 Xapian::Query(&src
));
111 mset
= enq
.get_mset(0, 10);
112 mset_expect_order(mset
, 5, 7, 11, 13, 9);
115 // Test that trying to use PostingSource with the remote backend throws
116 // Xapian::UnimplementedError as expected (we need to register the class
117 // in xapian-tcpsrv/xapian-progsrv for this to work).
118 DEFINE_TESTCASE(externalsource2
, remote
) {
119 Xapian::Database
db(get_database("apitest_phrase"));
120 Xapian::Enquire
enq(db
);
121 MyOddPostingSource
src(db
);
123 enq
.set_query(Xapian::Query(&src
));
125 TEST_EXCEPTION(Xapian::UnimplementedError
,
126 Xapian::MSet mset
= enq
.get_mset(0, 10));
128 Xapian::Query
q(Xapian::Query::OP_FILTER
,
129 Xapian::Query("leav"),
130 Xapian::Query(&src
));
133 TEST_EXCEPTION(Xapian::UnimplementedError
,
134 Xapian::MSet mset
= enq
.get_mset(0, 10));
137 class MyOddWeightingPostingSource
: public Xapian::PostingSource
{
138 Xapian::doccount num_docs
;
140 Xapian::doccount last_docid
;
144 MyOddWeightingPostingSource(Xapian::doccount num_docs_
,
145 Xapian::doccount last_docid_
)
146 : num_docs(num_docs_
), last_docid(last_docid_
), did(0)
152 MyOddWeightingPostingSource(const Xapian::Database
&db
)
153 : num_docs(db
.get_doccount()), last_docid(db
.get_lastdocid()), did(0)
156 PostingSource
* clone() const {
157 return new MyOddWeightingPostingSource(num_docs
, last_docid
);
160 void init(const Xapian::Database
&) { did
= 0; }
162 double get_weight() const {
163 return (did
% 2) ? 1000 : 0.001;
166 // These bounds could be better, but that's not important here.
167 Xapian::doccount
get_termfreq_min() const { return 0; }
169 Xapian::doccount
get_termfreq_est() const { return num_docs
/ 2; }
171 Xapian::doccount
get_termfreq_max() const { return num_docs
; }
173 void next(double wt
) {
178 void skip_to(Xapian::docid to_did
, double wt
) {
183 bool at_end() const {
184 // Doesn't work if last_docid is 2^32 - 1.
185 return did
> last_docid
;
188 Xapian::docid
get_docid() const { return did
; }
190 string
get_description() const {
191 return "MyOddWeightingPostingSource";
195 // Like externalsource1, except we use the weight to favour odd documents.
196 DEFINE_TESTCASE(externalsource3
, backend
&& !remote
&& !multi
) {
197 // Doesn't work for remote without registering with the server.
198 // Doesn't work for multi because it checks the docid in the
200 Xapian::Database
db(get_database("apitest_phrase"));
201 Xapian::Enquire
enq(db
);
202 MyOddWeightingPostingSource
src(db
);
204 enq
.set_query(Xapian::Query(&src
));
206 Xapian::MSet mset
= enq
.get_mset(0, 10);
207 mset_expect_order(mset
, 1, 3, 5, 7, 9, 11, 13, 15, 17, 2);
209 Xapian::Query
q(Xapian::Query::OP_OR
,
210 Xapian::Query("leav"),
211 Xapian::Query(&src
));
214 mset
= enq
.get_mset(0, 5);
215 mset_expect_order(mset
, 5, 7, 11, 13, 9);
217 tout
<< "max possible weight = " << mset
.get_max_possible() << endl
;
218 TEST(mset
.get_max_possible() > 1000);
220 enq
.set_cutoff(0, 1000.001);
221 mset
= enq
.get_mset(0, 10);
222 mset_expect_order(mset
, 5, 7, 11, 13, 9);
224 tout
<< "max possible weight = " << mset
.get_max_possible() << endl
;
225 TEST(mset
.get_max_possible() > 1000);
227 enq
.set_query(Xapian::Query(q
.OP_SCALE_WEIGHT
, Xapian::Query(&src
), 0.5));
228 mset
= enq
.get_mset(0, 10);
231 TEST_EQUAL(mset
.get_max_possible(), 500);
233 enq
.set_query(Xapian::Query(q
.OP_SCALE_WEIGHT
, Xapian::Query(&src
), 2));
234 mset
= enq
.get_mset(0, 10);
235 mset_expect_order(mset
, 1, 3, 5, 7, 9, 11, 13, 15, 17);
237 TEST_EQUAL(mset
.get_max_possible(), 2000);
240 class MyDontAskWeightPostingSource
: public Xapian::PostingSource
{
241 Xapian::doccount num_docs
;
243 Xapian::doccount last_docid
;
247 MyDontAskWeightPostingSource(Xapian::doccount num_docs_
,
248 Xapian::doccount last_docid_
)
249 : num_docs(num_docs_
), last_docid(last_docid_
), did(0)
253 MyDontAskWeightPostingSource() : Xapian::PostingSource() {}
255 PostingSource
* clone() const { return new MyDontAskWeightPostingSource(num_docs
, last_docid
); }
257 void init(const Xapian::Database
&db
) {
258 num_docs
= db
.get_doccount();
259 last_docid
= db
.get_lastdocid();
263 double get_weight() const {
264 FAIL_TEST("MyDontAskWeightPostingSource::get_weight() called");
267 // These bounds could be better, but that's not important here.
268 Xapian::doccount
get_termfreq_min() const { return num_docs
; }
270 Xapian::doccount
get_termfreq_est() const { return num_docs
; }
272 Xapian::doccount
get_termfreq_max() const { return num_docs
; }
274 void next(double wt
) {
279 void skip_to(Xapian::docid to_did
, double wt
) {
284 bool at_end() const {
285 // Doesn't work if last_docid is 2^32 - 1.
286 return did
> last_docid
;
289 Xapian::docid
get_docid() const { return did
; }
291 string
get_description() const {
292 return "MyDontAskWeightPostingSource";
296 // Check that boolean use doesn't call get_weight().
297 DEFINE_TESTCASE(externalsource4
, backend
&& !remote
) {
298 Xapian::Database
db(get_database("apitest_phrase"));
299 Xapian::Enquire
enq(db
);
300 MyDontAskWeightPostingSource src
;
302 tout
<< "OP_SCALE_WEIGHT 0" << endl
;
303 enq
.set_query(Xapian::Query(Xapian::Query::OP_SCALE_WEIGHT
, Xapian::Query(&src
), 0));
305 Xapian::MSet mset
= enq
.get_mset(0, 5);
306 mset_expect_order(mset
, 1, 2, 3, 4, 5);
308 tout
<< "OP_FILTER" << endl
;
309 Xapian::Query
q(Xapian::Query::OP_FILTER
,
310 Xapian::Query("leav"),
311 Xapian::Query(&src
));
314 mset
= enq
.get_mset(0, 5);
315 mset_expect_order(mset
, 8, 6, 4, 5, 7);
317 tout
<< "BoolWeight" << endl
;
318 enq
.set_query(Xapian::Query(&src
));
319 enq
.set_weighting_scheme(Xapian::BoolWeight());
321 // mset = enq.get_mset(0, 5);
322 // mset_expect_order(mset, 1, 2, 3, 4, 5);
325 // Check that valueweightsource works correctly.
326 DEFINE_TESTCASE(valueweightsource1
, backend
) {
327 Xapian::Database
db(get_database("apitest_phrase"));
328 Xapian::Enquire
enq(db
);
329 Xapian::ValueWeightPostingSource
src(11);
331 // Should be in descending order of length
332 tout
<< "RAW" << endl
;
333 enq
.set_query(Xapian::Query(&src
));
334 Xapian::MSet mset
= enq
.get_mset(0, 5);
335 mset_expect_order(mset
, 3, 1, 2, 8, 14);
337 // In relevance order
338 tout
<< "OP_FILTER" << endl
;
339 Xapian::Query
q(Xapian::Query::OP_FILTER
,
340 Xapian::Query("leav"),
341 Xapian::Query(&src
));
343 mset
= enq
.get_mset(0, 5);
344 mset_expect_order(mset
, 8, 6, 4, 5, 7);
346 // Should be in descending order of length
347 tout
<< "OP_FILTER other way" << endl
;
348 q
= Xapian::Query(Xapian::Query::OP_FILTER
,
350 Xapian::Query("leav"));
352 mset
= enq
.get_mset(0, 5);
353 mset_expect_order(mset
, 8, 14, 9, 13, 7);
356 // Check that valueweightsource gives the correct bounds for those databases
357 // which support value statistics.
358 DEFINE_TESTCASE(valueweightsource2
, valuestats
) {
359 Xapian::Database
db(get_database("apitest_phrase"));
360 Xapian::ValueWeightPostingSource
src(11);
362 TEST_EQUAL(src
.get_termfreq_min(), 17);
363 TEST_EQUAL(src
.get_termfreq_est(), 17);
364 TEST_EQUAL(src
.get_termfreq_max(), 17);
365 TEST_EQUAL(src
.get_maxweight(), 135);
368 // Check that valueweightsource skip_to() can stay in the same position.
369 DEFINE_TESTCASE(valueweightsource3
, valuestats
&& !multi
) {
370 // FIXME: multi doesn't support iterating valuestreams yet.
371 Xapian::Database
db(get_database("apitest_phrase"));
372 Xapian::ValueWeightPostingSource
src(11);
377 TEST_EQUAL(src
.get_docid(), 8);
380 TEST_EQUAL(src
.get_docid(), 8);
383 // Check that fixedweightsource works correctly.
384 DEFINE_TESTCASE(fixedweightsource1
, backend
) {
385 Xapian::Database
db(get_database("apitest_phrase"));
386 Xapian::Enquire
enq(db
);
390 Xapian::FixedWeightPostingSource
src(wt
);
392 // Should be in increasing order of docid.
393 enq
.set_query(Xapian::Query(&src
));
394 Xapian::MSet mset
= enq
.get_mset(0, 5);
395 mset_expect_order(mset
, 1, 2, 3, 4, 5);
397 for (Xapian::MSetIterator i
= mset
.begin(); i
!= mset
.end(); ++i
) {
398 TEST_EQUAL(i
.get_weight(), wt
);
402 // Do some direct tests, to check the skip_to() and check() methods work.
404 // Check next and skip_to().
405 Xapian::FixedWeightPostingSource
src(wt
);
410 TEST_EQUAL(src
.get_docid(), 1);
413 TEST_EQUAL(src
.get_docid(), 2);
416 TEST_EQUAL(src
.get_docid(), 5);
421 // Check check() as the first operation, followed by next.
422 Xapian::FixedWeightPostingSource
src(wt
);
425 TEST_EQUAL(src
.check(5, 1.0), true);
427 TEST_EQUAL(src
.get_docid(), 5);
430 TEST_EQUAL(src
.get_docid(), 6);
433 // Check check() as the first operation, followed by skip_to().
434 Xapian::FixedWeightPostingSource
src(wt
);
437 TEST_EQUAL(src
.check(5, 1.0), true);
439 TEST_EQUAL(src
.get_docid(), 5);
442 TEST_EQUAL(src
.get_docid(), 6);
443 src
.skip_to(7, wt
* 2);
448 // A posting source which changes the maximum weight.
449 class ChangeMaxweightPostingSource
: public Xapian::PostingSource
{
452 // Maximum docid that get_weight() should be called for.
453 Xapian::docid maxid_accessed
;
456 ChangeMaxweightPostingSource(Xapian::docid maxid_accessed_
)
457 : did(0), maxid_accessed(maxid_accessed_
) { }
459 void init(const Xapian::Database
&) { did
= 0; }
461 double get_weight() const {
462 if (did
> maxid_accessed
) {
463 FAIL_TEST("ChangeMaxweightPostingSource::get_weight() called "
464 "for docid " + str(did
) + ", max id accessed "
465 "should be " + str(maxid_accessed
));
470 Xapian::doccount
get_termfreq_min() const { return 4; }
471 Xapian::doccount
get_termfreq_est() const { return 4; }
472 Xapian::doccount
get_termfreq_max() const { return 4; }
476 set_maxweight(5 - did
);
479 void skip_to(Xapian::docid to_did
, double) {
481 set_maxweight(5 - did
);
484 bool at_end() const { return did
>= 5; }
485 Xapian::docid
get_docid() const { return did
; }
486 string
get_description() const { return "ChangeMaxweightPostingSource"; }
489 // Test a posting source with a variable maxweight.
490 DEFINE_TESTCASE(changemaxweightsource1
, backend
&& !remote
&& !multi
) {
491 // The ChangeMaxweightPostingSource doesn't work with multi or remote.
492 Xapian::Database
db(get_database("apitest_phrase"));
493 Xapian::Enquire
enq(db
);
496 ChangeMaxweightPostingSource
src1(5);
497 Xapian::FixedWeightPostingSource
src2(2.5);
499 Xapian::Query
q(Xapian::Query::OP_AND
,
500 Xapian::Query(&src1
), Xapian::Query(&src2
));
502 // Set descending docid order so that the matcher isn't able to
503 // terminate early after 4 documents just because weight == maxweight.
504 enq
.set_docid_order(enq
.DESCENDING
);
506 Xapian::MSet mset
= enq
.get_mset(0, 4);
508 mset_expect_order(mset
, 1, 2, 3, 4);
509 for (Xapian::MSetIterator i
= mset
.begin(); i
!= mset
.end(); ++i
) {
510 TEST_EQUAL_DOUBLE(i
.get_weight(), 7.5 - *i
);
515 ChangeMaxweightPostingSource
src1(3);
516 Xapian::FixedWeightPostingSource
src2(2.5);
518 Xapian::Query
q(Xapian::Query::OP_AND
,
519 Xapian::Query(&src1
), Xapian::Query(&src2
));
522 Xapian::MSet mset
= enq
.get_mset(0, 2);
523 TEST(!src1
.at_end());
524 TEST_EQUAL(src1
.get_docid(), 3);
525 TEST_EQUAL_DOUBLE(src1
.get_maxweight(), 2.0);
526 mset_expect_order(mset
, 1, 2);
527 for (Xapian::MSetIterator i
= mset
.begin(); i
!= mset
.end(); ++i
) {
528 TEST_EQUAL_DOUBLE(i
.get_weight(), 7.5 - *i
);
533 // Test using a valueweightpostingsource which has no entries.
534 DEFINE_TESTCASE(emptyvalwtsource1
, backend
&& !remote
&& !multi
) {
535 Xapian::Database
db(get_database("apitest_phrase"));
536 Xapian::Enquire
enq(db
);
538 Xapian::ValueWeightPostingSource
src2(11); // A non-empty slot.
539 Xapian::ValueWeightPostingSource
src3(100); // An empty slot.
540 Xapian::Query
q1("leav");
541 Xapian::Query
q2(&src2
);
542 Xapian::Query
q3(&src3
);
543 Xapian::Query
q(Xapian::Query::OP_OR
, Xapian::Query(Xapian::Query::OP_AND_MAYBE
, q1
, q2
), q3
);
545 // Perform search without ORring with the posting source.
546 Xapian::doccount size1
;
549 Xapian::MSet mset
= enq
.get_mset(0, 10);
550 TEST_REL(mset
.get_max_possible(), >, 0.0);
552 TEST_REL(size1
, >, 0);
555 // Perform a search with just the non-empty posting source, checking it
556 // returns something.
559 Xapian::MSet mset
= enq
.get_mset(0, 10);
560 TEST_REL(mset
.get_max_possible(), >, 0.0);
561 TEST_REL(mset
.size(), >, 0);
564 // Perform a search with just the empty posting source, checking it returns
568 Xapian::MSet mset
= enq
.get_mset(0, 10);
570 // get_max_possible() returns 0 here for backends which track the upper
571 // bound on value slot entries, MAX_DBL for backends which don't.
573 TEST_REL(mset
.get_max_possible(), >=, 0.0);
575 TEST_EQUAL(mset
.size(), 0);
578 // Perform a search with the posting source ORred with the normal query.
579 // This is a regression test - it used to return nothing.
582 Xapian::MSet mset
= enq
.get_mset(0, 10);
583 TEST_REL(mset
.get_max_possible(), >, 0.0);
584 TEST_REL(mset
.size(), >, 0.0);
585 TEST_EQUAL(mset
.size(), size1
);
589 class SlowDecreasingValueWeightPostingSource
590 : public Xapian::DecreasingValueWeightPostingSource
{
594 SlowDecreasingValueWeightPostingSource(int & count_
)
595 : Xapian::DecreasingValueWeightPostingSource(0), count(count_
) { }
597 SlowDecreasingValueWeightPostingSource
* clone() const
599 return new SlowDecreasingValueWeightPostingSource(count
);
602 void next(double min_wt
) {
605 return Xapian::DecreasingValueWeightPostingSource::next(min_wt
);
610 make_matchtimelimit1_db(Xapian::WritableDatabase
&db
, const string
&)
612 for (int wt
= 20; wt
> 0; --wt
) {
613 Xapian::Document doc
;
614 doc
.add_value(0, Xapian::sortable_serialise(double(wt
)));
615 db
.add_document(doc
);
619 // FIXME: This doesn't run for remote databases (we'd need to register
620 // SlowDecreasingValueWeightPostingSource on the remote).
621 DEFINE_TESTCASE(matchtimelimit1
, generated
&& !remote
)
623 #ifndef HAVE_TIMER_CREATE
624 SKIP_TEST("Enquire::set_time_limit() not implemented for this platform");
626 Xapian::Database db
= get_database("matchtimelimit1",
627 make_matchtimelimit1_db
);
630 SlowDecreasingValueWeightPostingSource
src(count
);
632 Xapian::Enquire
enquire(db
);
633 enquire
.set_query(Xapian::Query(&src
));
635 enquire
.set_time_limit(1.5);
637 Xapian::MSet mset
= enquire
.get_mset(0, 1, 1000);
638 TEST_EQUAL(mset
.size(), 1);
639 TEST_EQUAL(count
, 2);
642 class CheckBoundsPostingSource
643 : public Xapian::DecreasingValueWeightPostingSource
{
645 Xapian::doccount
& doclen_lb
;
647 Xapian::doccount
& doclen_ub
;
649 CheckBoundsPostingSource(Xapian::doccount
& doclen_lb_
,
650 Xapian::doccount
& doclen_ub_
)
651 : Xapian::DecreasingValueWeightPostingSource(0),
652 doclen_lb(doclen_lb_
),
653 doclen_ub(doclen_ub_
) { }
655 CheckBoundsPostingSource
* clone() const
657 return new CheckBoundsPostingSource(doclen_lb
, doclen_ub
);
660 void init(const Xapian::Database
& database
) {
661 doclen_lb
= database
.get_doclength_lower_bound();
662 doclen_ub
= database
.get_doclength_upper_bound();
663 Xapian::DecreasingValueWeightPostingSource::init(database
);
667 // Test that doclength bounds are correct.
668 // Regression test for bug fixed in 1.2.25 and 1.4.1.
669 DEFINE_TESTCASE(postingsourcebounds1
, backend
&& !remote
)
671 Xapian::Database db
= get_database("apitest_simpledata");
673 Xapian::doccount doclen_lb
= 0, doclen_ub
= 0;
674 CheckBoundsPostingSource
ps(doclen_lb
, doclen_ub
);
676 Xapian::Enquire
enquire(db
);
677 enquire
.set_query(Xapian::Query(&ps
));
679 Xapian::MSet mset
= enquire
.get_mset(0, 1);
681 TEST_EQUAL(doclen_lb
, db
.get_doclength_lower_bound());
682 TEST_EQUAL(doclen_ub
, db
.get_doclength_upper_bound());
685 // PostingSource which really just counts the clone() calls.
686 // Never actually matches anything, but pretends it might.
687 class CloneTestPostingSource
: public Xapian::PostingSource
{
691 CloneTestPostingSource(int& clone_count_
)
692 : clone_count(clone_count_
)
695 PostingSource
* clone() const {
697 return new CloneTestPostingSource(clone_count
);
700 void init(const Xapian::Database
&) { }
702 Xapian::doccount
get_termfreq_min() const { return 0; }
704 Xapian::doccount
get_termfreq_est() const { return 1; }
706 Xapian::doccount
get_termfreq_max() const { return 2; }
708 void next(double) { }
710 void skip_to(Xapian::docid
, double) { }
712 bool at_end() const {
716 Xapian::docid
get_docid() const { return 0; }
718 string
get_description() const { return "CloneTestPostingSource"; }
721 /// Test cloning of initial object, which regressed in 1.3.5.
722 DEFINE_TESTCASE(postingsourceclone1
, !backend
)
724 // This fails with 1.3.5-1.4.0 inclusive.
727 CloneTestPostingSource
ps(clones
);
728 TEST_EQUAL(clones
, 0);
729 Xapian::Query
q(&ps
);
730 TEST_EQUAL(clones
, 1);
733 // Check that clone() isn't needlessly called if reference counting has
734 // been turned on for the PostingSource.
737 CloneTestPostingSource
* ps
= new CloneTestPostingSource(clones
);
738 TEST_EQUAL(clones
, 0);
739 Xapian::Query
q(ps
->release());
740 TEST_EQUAL(clones
, 0);
744 class OnlyTheFirstPostingSource
: public Xapian::PostingSource
{
745 Xapian::doccount last_docid
;
752 static Xapian::doccount shard_index
;
755 OnlyTheFirstPostingSource(bool allow_clone_
) : allow_clone(allow_clone_
) {}
757 PostingSource
* clone() const {
758 return allow_clone
? new OnlyTheFirstPostingSource(true) : nullptr;
761 void init(const Xapian::Database
& db
) {
763 if (shard_index
== 0) {
764 last_docid
= db
.get_lastdocid();
771 Xapian::doccount
get_termfreq_min() const { return 0; }
773 Xapian::doccount
get_termfreq_est() const { return last_docid
/ 2; }
775 Xapian::doccount
get_termfreq_max() const { return last_docid
; }
777 void next(double wt
) {
780 if (did
> last_docid
) did
= 0;
783 void skip_to(Xapian::docid to_did
, double wt
) {
786 if (did
> last_docid
) did
= 0;
789 bool at_end() const {
793 Xapian::docid
get_docid() const { return did
; }
795 string
get_description() const { return "OnlyTheFirstPostingSource"; }
798 Xapian::doccount
OnlyTheFirstPostingSource::shard_index
;
800 DEFINE_TESTCASE(postingsourceshardindex1
, multi
&& !remote
) {
801 Xapian::Database db
= get_database("apitest_simpledata");
803 OnlyTheFirstPostingSource::shard_index
= 0;
805 Xapian::Enquire
enquire(db
);
807 auto ps
= new OnlyTheFirstPostingSource(true);
808 enquire
.set_query(Xapian::Query(ps
->release()));
810 Xapian::MSet mset
= enquire
.get_mset(0, 10);
811 mset_expect_order(mset
, 1, 3, 5);
815 /* Regression test for bug fixed in 1.4.12 - we should get an exception
816 * if we use a PostingSource that doesn't support clone() with a multi
819 auto ps
= new OnlyTheFirstPostingSource(false);
820 enquire
.set_query(Xapian::Query(ps
->release()));
822 TEST_EXCEPTION(Xapian::InvalidOperationError
,
823 auto m
= enquire
.get_mset(0, 10));
827 /// PostingSource subclass for injecting tf bounds and estimate.
828 class EstimatePS
: public Xapian::PostingSource
{
829 Xapian::doccount lb
, est
, ub
;
832 EstimatePS(Xapian::doccount lb_
,
833 Xapian::doccount est_
,
834 Xapian::doccount ub_
)
835 : lb(lb_
), est(est_
), ub(ub_
)
838 PostingSource
* clone() const { return new EstimatePS(lb
, est
, ub
); }
840 void init(const Xapian::Database
&) { }
842 Xapian::doccount
get_termfreq_min() const { return lb
; }
844 Xapian::doccount
get_termfreq_est() const { return est
; }
846 Xapian::doccount
get_termfreq_max() const { return ub
; }
849 FAIL_TEST("EstimatePS::next() shouldn't be called");
852 void skip_to(Xapian::docid
, double) {
853 FAIL_TEST("EstimatePS::skip_to() shouldn't be called");
856 bool at_end() const {
860 Xapian::docid
get_docid() const {
861 FAIL_TEST("EstimatePS::get_docid() shouldn't be called");
864 string
get_description() const { return "EstimatePS"; }
867 /// Check estimate is rounded to suitable number of S.F. - new in 1.4.3.
868 DEFINE_TESTCASE(estimaterounding1
, backend
&& !multi
&& !remote
) {
869 Xapian::Database db
= get_database("etext");
870 Xapian::Enquire
enquire(db
);
871 static const struct { Xapian::doccount lb
, est
, ub
, exp
; } testcases
[] = {
872 // Test rounding down.
873 {411, 424, 439, 420},
876 {411, 426, 439, 430},
877 {123, 351, 439, 400},
878 // Rounding based on estimate size if smaller than range size.
880 // Round "5" away from the nearer bound.
883 // Check we round up if rounding down would be out of range.
884 {411, 416, 439, 420},
885 {411, 412, 439, 420},
886 // Check we round down if rounding up would be out of range.
887 {111, 133, 138, 130},
888 {111, 137, 138, 130},
889 // Check we don't round if either way would be out of range.
890 {411, 415, 419, 415},
891 // Leave small estimates alone.
894 for (auto& t
: testcases
) {
895 EstimatePS
ps(t
.lb
, t
.est
, t
.ub
);
896 enquire
.set_query(Xapian::Query(&ps
));
897 Xapian::MSet mset
= enquire
.get_mset(0, 0);
898 // MSet::get_description() includes bounds and raw estimate.
899 tout
<< mset
.get_description() << endl
;
900 TEST_EQUAL(mset
.get_matches_estimated(), t
.exp
);