1 /** @file api_opvalue.cc
2 * @brief Tests of the OP_VALUE_* query operators.
4 /* Copyright 2007,2008,2009,2010,2010,2011,2017 Olly Betts
5 * Copyright 2008 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_opvalue.h"
31 #include "testsuite.h"
32 #include "testutils.h"
38 // Feature test for Query::OP_VALUE_RANGE.
39 DEFINE_TESTCASE(valuerange1
, backend
) {
40 Xapian::Database
db(get_database("apitest_phrase"));
41 Xapian::Enquire
enq(db
);
42 static const char * const vals
[] = {
43 "", " ", "a", "aa", "abcd", "e", "g", "h", "hzz", "i", "l", "z"
45 for (auto start
: vals
) {
46 for (auto end
: vals
) {
47 Xapian::Query
query(Xapian::Query::OP_VALUE_RANGE
, 1, start
, end
);
49 Xapian::MSet mset
= enq
.get_mset(0, 20);
50 // Check that documents in the MSet match the value range filter.
51 set
<Xapian::docid
> matched
;
52 Xapian::MSetIterator i
;
53 for (i
= mset
.begin(); i
!= mset
.end(); ++i
) {
55 string value
= db
.get_document(*i
).get_value(1);
56 TEST_REL(value
,>=,start
);
57 TEST_REL(value
,<=,end
);
59 // Check that documents not in the MSet don't match the value range filter.
60 for (Xapian::docid j
= db
.get_lastdocid(); j
!= 0; --j
) {
61 if (matched
.find(j
) == matched
.end()) {
62 string value
= db
.get_document(j
).get_value(1);
63 tout
<< value
<< " < '" << start
<< "' or > '" << end
<< "'" << endl
;
64 TEST(value
< start
|| value
> end
);
72 // Regression test for Query::OP_VALUE_LE - used to return document IDs for
73 // non-existent documents.
74 DEFINE_TESTCASE(valuerange2
, writable
) {
75 Xapian::WritableDatabase db
= get_writable_database();
78 doc
.add_value(0, "5");
79 db
.replace_document(5, doc
);
80 Xapian::Enquire
enq(db
);
82 Xapian::Query
query(Xapian::Query::OP_VALUE_LE
, 0, "6");
84 Xapian::MSet mset
= enq
.get_mset(0, 20);
86 TEST_EQUAL(mset
.size(), 1);
87 TEST_EQUAL(*(mset
[0]), 5);
92 make_valuerange5(Xapian::WritableDatabase
&db
, const string
&)
95 doc
.add_value(0, "BOOK");
97 doc
.add_value(0, "VOLUME");
101 // Check that lower and upper bounds are used.
102 DEFINE_TESTCASE(valuerange5
, generated
) {
103 Xapian::Database db
= get_database("valuerange5", make_valuerange5
);
105 // If the lower bound is empty, either the specified value slot is
106 // never used in the database, or the backend doesn't track value bounds.
107 // Neither should be true here.
108 TEST(!db
.get_value_lower_bound(0).empty());
110 Xapian::Enquire
enq(db
);
112 Xapian::Query
query(Xapian::Query::OP_VALUE_RANGE
, 0, "APPLE", "BANANA");
113 enq
.set_query(query
);
114 Xapian::MSet mset
= enq
.get_mset(0, 0);
115 TEST_EQUAL(mset
.get_matches_estimated(), 0);
117 Xapian::Query
query2(Xapian::Query::OP_VALUE_RANGE
, 0, "WALRUS", "ZEBRA");
118 enq
.set_query(query2
);
119 mset
= enq
.get_mset(0, 0);
120 TEST_EQUAL(mset
.get_matches_estimated(), 0);
126 make_singularvalue_db(Xapian::WritableDatabase
&db
, const string
&)
128 Xapian::Document doc
;
129 db
.add_document(doc
);
130 doc
.add_value(0, "SINGULAR");
131 db
.add_document(doc
);
132 db
.add_document(doc
);
135 // Check handling of bounds when bounds are equal.
136 DEFINE_TESTCASE(valuerange6
, generated
) {
137 const auto OP_VALUE_RANGE
= Xapian::Query::OP_VALUE_RANGE
;
138 Xapian::Database db
= get_database("singularvalue", make_singularvalue_db
);
140 Xapian::Enquire
enq(db
);
143 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "SATSUMA", "SLOE");
144 enq
.set_query(query
);
145 Xapian::MSet mset
= enq
.get_mset(0, 0);
146 TEST_EQUAL(mset
.get_matches_lower_bound(), 2);
147 TEST_EQUAL(mset
.get_matches_estimated(), 2);
148 TEST_EQUAL(mset
.get_matches_upper_bound(), 2);
150 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "PEACH", "PLUM");
151 enq
.set_query(query
);
152 mset
= enq
.get_mset(0, 0);
153 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
154 TEST_EQUAL(mset
.get_matches_estimated(), 0);
155 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
157 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "PEACH", "PEACH");
158 enq
.set_query(query
);
159 mset
= enq
.get_mset(0, 0);
160 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
161 TEST_EQUAL(mset
.get_matches_estimated(), 0);
162 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
164 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "PEACH", "PEACHERINE");
165 enq
.set_query(query
);
166 mset
= enq
.get_mset(0, 0);
167 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
168 TEST_EQUAL(mset
.get_matches_estimated(), 0);
169 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
171 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "SING", "SINGULARITY");
172 enq
.set_query(query
);
173 mset
= enq
.get_mset(0, 0);
174 TEST_EQUAL(mset
.get_matches_lower_bound(), 2);
175 TEST_EQUAL(mset
.get_matches_estimated(), 2);
176 TEST_EQUAL(mset
.get_matches_upper_bound(), 2);
178 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "SING", "SINGULAR");
179 enq
.set_query(query
);
180 mset
= enq
.get_mset(0, 0);
181 TEST_EQUAL(mset
.get_matches_lower_bound(), 2);
182 TEST_EQUAL(mset
.get_matches_estimated(), 2);
183 TEST_EQUAL(mset
.get_matches_upper_bound(), 2);
185 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "SINGULAR", "SINGULARITY");
186 enq
.set_query(query
);
187 mset
= enq
.get_mset(0, 0);
188 TEST_EQUAL(mset
.get_matches_lower_bound(), 2);
189 TEST_EQUAL(mset
.get_matches_estimated(), 2);
190 TEST_EQUAL(mset
.get_matches_upper_bound(), 2);
192 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "SINGULAR", "SINGULAR");
193 enq
.set_query(query
);
194 mset
= enq
.get_mset(0, 0);
195 TEST_EQUAL(mset
.get_matches_lower_bound(), 2);
196 TEST_EQUAL(mset
.get_matches_estimated(), 2);
197 TEST_EQUAL(mset
.get_matches_upper_bound(), 2);
199 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "SINGULARITY", "SINGULARITY");
200 enq
.set_query(query
);
201 mset
= enq
.get_mset(0, 0);
202 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
203 TEST_EQUAL(mset
.get_matches_estimated(), 0);
204 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
206 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "SINGULARITY", "SINGULARITIES");
207 enq
.set_query(query
);
208 mset
= enq
.get_mset(0, 0);
209 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
210 TEST_EQUAL(mset
.get_matches_estimated(), 0);
211 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
213 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "SINGULARITY", "SINNER");
214 enq
.set_query(query
);
215 mset
= enq
.get_mset(0, 0);
216 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
217 TEST_EQUAL(mset
.get_matches_estimated(), 0);
218 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
220 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "SINGULARITY", "ZEBRA");
221 enq
.set_query(query
);
222 mset
= enq
.get_mset(0, 0);
223 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
224 TEST_EQUAL(mset
.get_matches_estimated(), 0);
225 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
227 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "SINGE", "SINGER");
228 enq
.set_query(query
);
229 mset
= enq
.get_mset(0, 0);
230 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
231 TEST_EQUAL(mset
.get_matches_estimated(), 0);
232 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
234 // Check no assertions when slot is empty. Regression test for bug
235 // introduced and fixed between 1.4.5 and 1.4.6.
236 query
= Xapian::Query(OP_VALUE_RANGE
, 1, "MONK", "MONKEY");
237 enq
.set_query(query
);
238 mset
= enq
.get_mset(0, 0);
239 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
240 TEST_EQUAL(mset
.get_matches_estimated(), 0);
241 TEST_EQUAL(mset
.get_matches_upper_bound(), 0);
247 make_valprefixbounds_db(Xapian::WritableDatabase
&db
, const string
&)
249 Xapian::Document doc
;
250 db
.add_document(doc
);
251 doc
.add_value(0, "ZERO");
252 db
.add_document(doc
);
253 doc
.add_value(0, string("ZERO\0", 5));
254 db
.add_document(doc
);
257 // Check handling of bounds when low is a prefix of high.
258 DEFINE_TESTCASE(valuerange7
, generated
) {
259 const auto OP_VALUE_RANGE
= Xapian::Query::OP_VALUE_RANGE
;
260 Xapian::Database db
= get_database("valprefixbounds", make_valprefixbounds_db
);
262 Xapian::Enquire
enq(db
);
265 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "ZAP", "ZOO");
266 enq
.set_query(query
);
267 Xapian::MSet mset
= enq
.get_mset(0, 0);
268 TEST_EQUAL(mset
.get_matches_lower_bound(), 2);
269 TEST_EQUAL(mset
.get_matches_estimated(), 2);
270 TEST_EQUAL(mset
.get_matches_upper_bound(), 2);
272 query
= Xapian::Query(OP_VALUE_RANGE
, 0, "ZAP", "ZERO");
273 enq
.set_query(query
);
274 mset
= enq
.get_mset(0, 0);
275 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
276 TEST_EQUAL(mset
.get_matches_estimated(), 1);
277 TEST_EQUAL(mset
.get_matches_upper_bound(), 2);
282 // Feature test for Query::OP_VALUE_GE.
283 DEFINE_TESTCASE(valuege1
, backend
) {
284 Xapian::Database
db(get_database("apitest_phrase"));
285 Xapian::Enquire
enq(db
);
286 static const char * const vals
[] = {
287 "", " ", "a", "aa", "abcd", "e", "g", "h", "hzz", "i", "l", "z"
289 for (auto start
: vals
) {
290 Xapian::Query
query(Xapian::Query::OP_VALUE_GE
, 1, start
);
291 enq
.set_query(query
);
292 Xapian::MSet mset
= enq
.get_mset(0, 20);
293 // Check that documents in the MSet match the value range filter.
294 set
<Xapian::docid
> matched
;
295 Xapian::MSetIterator i
;
296 for (i
= mset
.begin(); i
!= mset
.end(); ++i
) {
298 string value
= db
.get_document(*i
).get_value(1);
299 tout
<< "'" << start
<< "' <= '" << value
<< "'" << endl
;
300 TEST_REL(value
,>=,start
);
302 // Check that documents not in the MSet don't match the value range
304 for (Xapian::docid j
= db
.get_lastdocid(); j
!= 0; --j
) {
305 if (matched
.find(j
) == matched
.end()) {
306 string value
= db
.get_document(j
).get_value(1);
307 tout
<< value
<< " < '" << start
<< "'" << endl
;
308 TEST_REL(value
,<,start
);
315 // Regression test for Query::OP_VALUE_GE - used to segfault if check() got
317 DEFINE_TESTCASE(valuege2
, backend
) {
318 Xapian::Database
db(get_database("apitest_phrase"));
319 Xapian::Enquire
enq(db
);
320 Xapian::Query
query(Xapian::Query::OP_AND
,
321 Xapian::Query("what"),
322 Xapian::Query(Xapian::Query::OP_VALUE_GE
, 1, "aa"));
323 enq
.set_query(query
);
324 Xapian::MSet mset
= enq
.get_mset(0, 20);
328 // Feature test for Query::OP_VALUE_LE.
329 DEFINE_TESTCASE(valuele1
, backend
) {
330 Xapian::Database
db(get_database("apitest_phrase"));
331 Xapian::Enquire
enq(db
);
332 static const char * const vals
[] = {
333 "", " ", "a", "aa", "abcd", "e", "g", "h", "hzz", "i", "l", "z"
335 for (auto end
: vals
) {
336 Xapian::Query
query(Xapian::Query::OP_VALUE_LE
, 1, end
);
337 enq
.set_query(query
);
338 Xapian::MSet mset
= enq
.get_mset(0, 20);
339 // Check that documents in the MSet match the value range filter.
340 set
<Xapian::docid
> matched
;
341 Xapian::MSetIterator i
;
342 for (i
= mset
.begin(); i
!= mset
.end(); ++i
) {
344 string value
= db
.get_document(*i
).get_value(1);
345 TEST_REL(value
,<=,end
);
347 // Check that documents not in the MSet don't match the value range
349 for (Xapian::docid j
= db
.get_lastdocid(); j
!= 0; --j
) {
350 if (matched
.find(j
) == matched
.end()) {
351 string value
= db
.get_document(j
).get_value(1);
352 TEST_REL(value
,>,end
);
359 // Check that Query(OP_VALUE_GE, 0, "") -> Query::MatchAll.
360 DEFINE_TESTCASE(valuege3
, !backend
) {
361 Xapian::Query
query(Xapian::Query::OP_VALUE_GE
, 0, "");
362 TEST_STRINGS_EQUAL(query
.get_description(), Xapian::Query::MatchAll
.get_description());
366 // Test Query::OP_VALUE_GE in a query which causes its skip_to() to be used.
367 DEFINE_TESTCASE(valuege4
, backend
) {
368 Xapian::Database
db(get_database("apitest_phrase"));
369 Xapian::Enquire
enq(db
);
371 // This query should put the ValueGePostList on the LHS of the AND because
372 // it has a lower estimated termfreq than the term "fridg". As a result,
373 // the skip_to() method is used to advance the ValueGePostList.
374 Xapian::Query
query(Xapian::Query::OP_AND
,
375 Xapian::Query("fridg"),
376 Xapian::Query(Xapian::Query::OP_VALUE_GE
, 1, "aa"));
377 enq
.set_query(query
);
378 Xapian::MSet mset
= enq
.get_mset(0, 20);
382 // Test Query::OP_VALUE_RANGE in a query which causes its check() to be used.
383 DEFINE_TESTCASE(valuerange3
, backend
) {
384 Xapian::Database
db(get_database("apitest_phrase"));
385 Xapian::Enquire
enq(db
);
386 Xapian::Query
query(Xapian::Query::OP_AND
,
387 Xapian::Query("what"),
388 Xapian::Query(Xapian::Query::OP_VALUE_RANGE
, 1,
390 enq
.set_query(query
);
391 Xapian::MSet mset
= enq
.get_mset(0, 20);
395 // Test Query::OP_VALUE_RANGE in a query which causes its skip_to() to be used.
396 DEFINE_TESTCASE(valuerange4
, backend
) {
397 Xapian::Database
db(get_database("apitest_phrase"));
398 Xapian::Enquire
enq(db
);
399 Xapian::Query
query(Xapian::Query::OP_AND
,
400 Xapian::Query("fridg"),
401 Xapian::Query(Xapian::Query::OP_VALUE_RANGE
, 1,
403 enq
.set_query(query
);
404 Xapian::MSet mset
= enq
.get_mset(0, 20);
408 /// Test improved upper bound and estimate in 1.4.3.
409 DEFINE_TESTCASE(valuerangematchesub1
, backend
) {
410 Xapian::Database
db(get_database("etext"));
411 Xapian::Enquire
enq(db
);
412 // Values present in slot 10 range from 'e' to 'w'.
413 Xapian::Query
query(Xapian::Query(Xapian::Query::OP_VALUE_RANGE
, 10,
415 enq
.set_query(query
);
416 Xapian::MSet mset
= enq
.get_mset(0, 0);
417 // The upper bound used to be db.size().
418 TEST_EQUAL(mset
.get_matches_upper_bound(), db
.get_value_freq(10));
419 TEST_EQUAL(mset
.get_matches_lower_bound(), 0);
420 // The estimate used to be db.size() / 2, now it's calculated
421 // proportional to the possible range.
422 TEST_REL(mset
.get_matches_estimated(), <=, db
.get_doccount() / 3);