Make all read-only data arrays static and const
[xapian.git] / xapian-core / tests / api_query.cc
blob1f7afe3812dba47b947025739af4f7b3ddb7533e
1 /** @file api_query.cc
2 * @brief Query-related tests.
3 */
4 /* Copyright (C) 2008,2009,2012,2013,2015,2016,2017 Olly Betts
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 * USA
22 #include <config.h>
24 #include "api_query.h"
26 #include <xapian.h>
28 #include "testsuite.h"
29 #include "testutils.h"
31 #include "apitest.h"
33 using namespace std;
35 /// Regression test - in 1.0.10 and earlier "" was included in the list.
36 DEFINE_TESTCASE(queryterms1, !backend) {
37 Xapian::Query query = Xapian::Query::MatchAll;
38 TEST(query.get_terms_begin() == query.get_terms_end());
39 query = Xapian::Query(query.OP_AND_NOT, query, Xapian::Query("fair"));
40 TEST_EQUAL(*query.get_terms_begin(), "fair");
41 return true;
44 DEFINE_TESTCASE(matchall2, !backend) {
45 TEST_STRINGS_EQUAL(Xapian::Query::MatchAll.get_description(),
46 "Query(<alldocuments>)");
47 return true;
50 DEFINE_TESTCASE(matchnothing1, !backend) {
51 TEST_STRINGS_EQUAL(Xapian::Query::MatchNothing.get_description(),
52 "Query()");
53 vector<Xapian::Query> subqs;
54 subqs.push_back(Xapian::Query("foo"));
55 subqs.push_back(Xapian::Query::MatchNothing);
56 Xapian::Query q(Xapian::Query::OP_AND, subqs.begin(), subqs.end());
57 TEST_STRINGS_EQUAL(q.get_description(), "Query()");
59 Xapian::Query q2(Xapian::Query::OP_AND,
60 Xapian::Query("foo"), Xapian::Query::MatchNothing);
61 TEST_STRINGS_EQUAL(q2.get_description(), "Query()");
62 return true;
65 DEFINE_TESTCASE(overload1, !backend) {
66 Xapian::Query q;
67 q = Xapian::Query("foo") & Xapian::Query("bar");
68 TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND bar))");
69 q = Xapian::Query("foo") &~ Xapian::Query("bar");
70 TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND_NOT bar))");
71 q = ~Xapian::Query("bar");
72 TEST_STRINGS_EQUAL(q.get_description(), "Query((<alldocuments> AND_NOT bar))");
73 q = Xapian::Query("foo") & Xapian::Query::MatchNothing;
74 TEST_STRINGS_EQUAL(q.get_description(), "Query()");
75 q = Xapian::Query("foo") | Xapian::Query("bar");
76 TEST_STRINGS_EQUAL(q.get_description(), "Query((foo OR bar))");
77 q = Xapian::Query("foo") | Xapian::Query::MatchNothing;
78 TEST_STRINGS_EQUAL(q.get_description(), "Query(foo)");
79 q = Xapian::Query("foo") ^ Xapian::Query("bar");
80 TEST_STRINGS_EQUAL(q.get_description(), "Query((foo XOR bar))");
81 q = Xapian::Query("foo") ^ Xapian::Query::MatchNothing;
82 TEST_STRINGS_EQUAL(q.get_description(), "Query(foo)");
83 q = 1.25 * (Xapian::Query("one") | Xapian::Query("two"));
84 TEST_STRINGS_EQUAL(q.get_description(), "Query(1.25 * (one OR two))");
85 q = (Xapian::Query("one") & Xapian::Query("two")) * 42;
86 TEST_STRINGS_EQUAL(q.get_description(), "Query(42 * (one AND two))");
87 q = Xapian::Query("one") / 2.0;
88 TEST_STRINGS_EQUAL(q.get_description(), "Query(0.5 * one)");
89 return true;
92 /** Regression test and feature test.
94 * This threw AssertionError in 1.0.9 and earlier (bug#201) and gave valgrind
95 * errors in 1.0.11 and earlier (bug#349).
97 * Currently the OR-subquery case is supported, other operators aren't.
99 DEFINE_TESTCASE(possubqueries1, writable) {
100 Xapian::WritableDatabase db = get_writable_database();
101 Xapian::Document doc;
102 doc.add_posting("a", 1);
103 doc.add_posting("b", 2);
104 doc.add_posting("c", 3);
105 db.add_document(doc);
107 Xapian::Query a_or_b(Xapian::Query::OP_OR,
108 Xapian::Query("a"),
109 Xapian::Query("b"));
110 Xapian::Query near(Xapian::Query::OP_NEAR, a_or_b, a_or_b);
111 // As of 1.3.0, we no longer rearrange queries at this point, so check
112 // that we don't.
113 TEST_STRINGS_EQUAL(near.get_description(),
114 "Query(((a OR b) NEAR 2 (a OR b)))");
115 Xapian::Query phrase(Xapian::Query::OP_PHRASE, a_or_b, a_or_b);
116 TEST_STRINGS_EQUAL(phrase.get_description(),
117 "Query(((a OR b) PHRASE 2 (a OR b)))");
119 Xapian::Query a_and_b(Xapian::Query::OP_AND,
120 Xapian::Query("a"),
121 Xapian::Query("b"));
122 Xapian::Query a_near_b(Xapian::Query::OP_NEAR,
123 Xapian::Query("a"),
124 Xapian::Query("b"));
125 Xapian::Query a_phrs_b(Xapian::Query::OP_PHRASE,
126 Xapian::Query("a"),
127 Xapian::Query("b"));
128 Xapian::Query c("c");
130 // FIXME: The plan is to actually try to support the cases below, but
131 // for now at least ensure they are cleanly rejected.
132 Xapian::Enquire enq(db);
134 TEST_EXCEPTION(Xapian::UnimplementedError,
135 Xapian::Query q(Xapian::Query::OP_NEAR, a_and_b, c);
136 enq.set_query(q);
137 (void)enq.get_mset(0, 10));
139 TEST_EXCEPTION(Xapian::UnimplementedError,
140 Xapian::Query q(Xapian::Query::OP_NEAR, a_near_b, c);
141 enq.set_query(q);
142 (void)enq.get_mset(0, 10));
144 TEST_EXCEPTION(Xapian::UnimplementedError,
145 Xapian::Query q(Xapian::Query::OP_NEAR, a_phrs_b, c);
146 enq.set_query(q);
147 (void)enq.get_mset(0, 10));
149 TEST_EXCEPTION(Xapian::UnimplementedError,
150 Xapian::Query q(Xapian::Query::OP_PHRASE, a_and_b, c);
151 enq.set_query(q);
152 (void)enq.get_mset(0, 10));
154 TEST_EXCEPTION(Xapian::UnimplementedError,
155 Xapian::Query q(Xapian::Query::OP_PHRASE, a_near_b, c);
156 enq.set_query(q);
157 (void)enq.get_mset(0, 10));
159 TEST_EXCEPTION(Xapian::UnimplementedError,
160 Xapian::Query q(Xapian::Query::OP_PHRASE, a_phrs_b, c);
161 enq.set_query(q);
162 (void)enq.get_mset(0, 10));
164 return true;
167 /// Test that XOR handles all remaining subqueries running out at the same
168 // time.
169 DEFINE_TESTCASE(xor3, backend) {
170 Xapian::Database db = get_database("apitest_simpledata");
172 static const char * const subqs[] = {
173 "hack", "which", "paragraph", "is", "return"
175 // Document where the subqueries run out *does* match XOR:
176 Xapian::Query q(Xapian::Query::OP_XOR, subqs, subqs + 5);
177 Xapian::Enquire enq(db);
178 enq.set_query(q);
179 Xapian::MSet mset = enq.get_mset(0, 10);
181 TEST_EQUAL(mset.size(), 3);
182 TEST_EQUAL(*mset[0], 4);
183 TEST_EQUAL(*mset[1], 2);
184 TEST_EQUAL(*mset[2], 3);
186 // Document where the subqueries run out *does not* match XOR:
187 q = Xapian::Query(Xapian::Query::OP_XOR, subqs, subqs + 4);
188 enq.set_query(q);
189 mset = enq.get_mset(0, 10);
191 TEST_EQUAL(mset.size(), 4);
192 TEST_EQUAL(*mset[0], 5);
193 TEST_EQUAL(*mset[1], 4);
194 TEST_EQUAL(*mset[2], 2);
195 TEST_EQUAL(*mset[3], 3);
197 return true;
200 /// Check encoding of non-UTF8 terms in query descriptions.
201 DEFINE_TESTCASE(nonutf8termdesc1, !backend) {
202 TEST_EQUAL(Xapian::Query("\xc0\x80\xf5\x80\x80\x80\xfe\xff").get_description(),
203 "Query(\\xc0\\x80\\xf5\\x80\\x80\\x80\\xfe\\xff)");
204 TEST_EQUAL(Xapian::Query(string("\x00\x1f", 2)).get_description(),
205 "Query(\\x00\\x1f)");
206 // Check that backslashes are encoded so output isn't ambiguous.
207 TEST_EQUAL(Xapian::Query("back\\slash").get_description(),
208 "Query(back\\x5cslash)");
209 // Check that \x7f is escaped.
210 TEST_EQUAL(Xapian::Query("D\x7f_\x7f~").get_description(),
211 "Query(D\\x7f_\\x7f~)");
212 return true;
215 /// Test introspection on Query objects.
216 DEFINE_TESTCASE(queryintro1, !backend) {
217 TEST_EQUAL(Xapian::Query::MatchAll.get_type(), Xapian::Query::LEAF_MATCH_ALL);
218 TEST_EQUAL(Xapian::Query::MatchAll.get_num_subqueries(), 0);
219 TEST_EQUAL(Xapian::Query::MatchNothing.get_type(), Xapian::Query::LEAF_MATCH_NOTHING);
220 TEST_EQUAL(Xapian::Query::MatchNothing.get_num_subqueries(), 0);
222 Xapian::Query q;
223 q = Xapian::Query(q.OP_AND_NOT, Xapian::Query::MatchAll, Xapian::Query("fair"));
224 TEST_EQUAL(q.get_type(), q.OP_AND_NOT);
225 TEST_EQUAL(q.get_num_subqueries(), 2);
226 TEST_EQUAL(q.get_subquery(0).get_type(), q.LEAF_MATCH_ALL);
227 TEST_EQUAL(q.get_subquery(1).get_type(), q.LEAF_TERM);
229 q = Xapian::Query("foo", 2, 1);
230 TEST_EQUAL(q.get_leaf_wqf(), 2);
231 TEST_EQUAL(q.get_leaf_pos(), 1);
233 q = Xapian::Query("bar");
234 TEST_EQUAL(q.get_leaf_wqf(), 1);
235 TEST_EQUAL(q.get_leaf_pos(), 0);
237 q = Xapian::Query("foo") & Xapian::Query("bar");
238 TEST_EQUAL(q.get_type(), q.OP_AND);
240 q = Xapian::Query("foo") &~ Xapian::Query("bar");
241 TEST_EQUAL(q.get_type(), q.OP_AND_NOT);
243 q = ~Xapian::Query("bar");
244 TEST_EQUAL(q.get_type(), q.OP_AND_NOT);
246 q = Xapian::Query("foo") | Xapian::Query("bar");
247 TEST_EQUAL(q.get_type(), q.OP_OR);
249 q = Xapian::Query("foo") ^ Xapian::Query("bar");
250 TEST_EQUAL(q.get_type(), q.OP_XOR);
252 q = 1.25 * (Xapian::Query("one") | Xapian::Query("two"));
253 TEST_EQUAL(q.get_type(), q.OP_SCALE_WEIGHT);
254 TEST_EQUAL(q.get_num_subqueries(), 1);
255 TEST_EQUAL(q.get_subquery(0).get_type(), q.OP_OR);
257 q = Xapian::Query("one") / 2.0;
258 TEST_EQUAL(q.get_type(), q.OP_SCALE_WEIGHT);
259 TEST_EQUAL(q.get_num_subqueries(), 1);
260 TEST_EQUAL(q.get_subquery(0).get_type(), q.LEAF_TERM);
262 q = Xapian::Query(q.OP_NEAR, Xapian::Query("a"), Xapian::Query("b"));
263 TEST_EQUAL(q.get_type(), q.OP_NEAR);
264 TEST_EQUAL(q.get_num_subqueries(), 2);
265 TEST_EQUAL(q.get_subquery(0).get_type(), q.LEAF_TERM);
266 TEST_EQUAL(q.get_subquery(1).get_type(), q.LEAF_TERM);
268 q = Xapian::Query(q.OP_PHRASE, Xapian::Query("c"), Xapian::Query("d"));
269 TEST_EQUAL(q.get_type(), q.OP_PHRASE);
270 TEST_EQUAL(q.get_num_subqueries(), 2);
271 TEST_EQUAL(q.get_subquery(0).get_type(), q.LEAF_TERM);
272 TEST_EQUAL(q.get_subquery(1).get_type(), q.LEAF_TERM);
274 return true;
277 /// Regression test for bug introduced in 1.3.1 and fixed in 1.3.3.
278 // We were incorrectly converting a term which indexed all docs and was used
279 // in an unweighted phrase into an all docs postlist, so check that this
280 // case actually works.
281 DEFINE_TESTCASE(phrasealldocs1, backend) {
282 Xapian::Database db = get_database("apitest_declen");
283 Xapian::Query q;
284 static const char * const phrase[] = { "this", "is", "the" };
285 q = Xapian::Query(q.OP_AND_NOT,
286 Xapian::Query("paragraph"),
287 Xapian::Query(q.OP_PHRASE, phrase, phrase + 3));
288 Xapian::Enquire enq(db);
289 enq.set_query(q);
290 Xapian::MSet mset = enq.get_mset(0, 10);
291 TEST_EQUAL(mset.size(), 3);
293 return true;
296 struct wildcard_testcase {
297 const char * pattern;
298 Xapian::termcount max_expansion;
299 char max_type;
300 const char * terms[4];
303 #define WILDCARD_EXCEPTION { 0, 0, 0, "" }
304 static const
305 wildcard_testcase wildcard1_testcases[] = {
306 // Tries to expand to 7 terms.
307 { "th", 6, 'E', WILDCARD_EXCEPTION },
308 { "thou", 1, 'E', { "though", 0, 0, 0 } },
309 { "s", 2, 'F', { "say", "search", 0, 0 } },
310 { "s", 2, 'M', { "simpl", "so", 0, 0 } },
311 { 0, 0, 0, { 0, 0, 0, 0 } }
314 DEFINE_TESTCASE(wildcard1, backend) {
315 // FIXME: The counting of terms the wildcard expands to is per subdatabase,
316 // so the wildcard may expand to more terms than the limit if some aren't
317 // in all subdatabases. Also WILDCARD_LIMIT_MOST_FREQUENT uses the
318 // frequency from the subdatabase, and so may select different terms in
319 // each subdatabase.
320 SKIP_TEST_FOR_BACKEND("multi");
321 Xapian::Database db = get_database("apitest_simpledata");
322 Xapian::Enquire enq(db);
323 const Xapian::Query::op o = Xapian::Query::OP_WILDCARD;
325 const wildcard_testcase * p = wildcard1_testcases;
326 while (p->pattern) {
327 tout << p->pattern << endl;
328 const char * const * tend = p->terms + 4;
329 while (tend[-1] == NULL) --tend;
330 bool expect_exception = (tend - p->terms == 4 && tend[-1][0] == '\0');
331 Xapian::Query q;
332 if (p->max_type) {
333 int max_type;
334 switch (p->max_type) {
335 case 'E':
336 max_type = Xapian::Query::WILDCARD_LIMIT_ERROR;
337 break;
338 case 'F':
339 max_type = Xapian::Query::WILDCARD_LIMIT_FIRST;
340 break;
341 case 'M':
342 max_type = Xapian::Query::WILDCARD_LIMIT_MOST_FREQUENT;
343 break;
344 default:
345 return false;
347 q = Xapian::Query(o, p->pattern, p->max_expansion, max_type);
348 } else {
349 q = Xapian::Query(o, p->pattern, p->max_expansion);
351 enq.set_query(q);
352 try {
353 Xapian::MSet mset = enq.get_mset(0, 10);
354 TEST(!expect_exception);
355 q = Xapian::Query(q.OP_SYNONYM, p->terms, tend);
356 enq.set_query(q);
357 Xapian::MSet mset2 = enq.get_mset(0, 10);
358 TEST_EQUAL(mset.size(), mset2.size());
359 TEST(mset_range_is_same(mset, 0, mset2, 0, mset.size()));
360 } catch (const Xapian::WildcardError &) {
361 TEST(expect_exception);
363 ++p;
366 return true;
369 /// Regression test for #696, fixed in 1.3.4.
370 DEFINE_TESTCASE(wildcard2, backend) {
371 // FIXME: The counting of terms the wildcard expands to is per subdatabase,
372 // so the wildcard may expand to more terms than the limit if some aren't
373 // in all subdatabases. Also WILDCARD_LIMIT_MOST_FREQUENT uses the
374 // frequency from the subdatabase, and so may select different terms in
375 // each subdatabase.
376 SKIP_TEST_FOR_BACKEND("multi");
377 Xapian::Database db = get_database("apitest_simpledata");
378 Xapian::Enquire enq(db);
379 const Xapian::Query::op o = Xapian::Query::OP_WILDCARD;
381 const int max_type = Xapian::Query::WILDCARD_LIMIT_MOST_FREQUENT;
382 Xapian::Query q0(o, "w", 2, max_type);
383 Xapian::Query q(o, "s", 2, max_type);
384 Xapian::Query q2(o, "t", 2, max_type);
385 q = Xapian::Query(q.OP_OR, q0, q);
386 q = Xapian::Query(q.OP_OR, q, q2);
387 enq.set_query(q);
388 Xapian::MSet mset = enq.get_mset(0, 10);
389 TEST_EQUAL(mset.size(), 6);
391 return true;
394 DEFINE_TESTCASE(dualprefixwildcard1, backend) {
395 Xapian::Database db = get_database("apitest_simpledata");
396 Xapian::Query q(Xapian::Query::OP_SYNONYM,
397 Xapian::Query(Xapian::Query::OP_WILDCARD, "fo"),
398 Xapian::Query(Xapian::Query::OP_WILDCARD, "Sfo"));
399 tout << q.get_description() << endl;
400 Xapian::Enquire enq(db);
401 enq.set_query(q);
402 TEST_EQUAL(enq.get_mset(0, 5).size(), 2);
403 return true;
406 struct positional_testcase {
407 int window;
408 const char * terms[4];
409 Xapian::docid result;
412 static const
413 positional_testcase loosephrase1_testcases[] = {
414 { 5, { "expect", "to", "mset", 0 }, 0 },
415 { 5, { "word", "well", "the", 0 }, 2 },
416 { 5, { "if", "word", "doesnt", 0 }, 0 },
417 { 5, { "at", "line", "three", 0 }, 0 },
418 { 5, { "paragraph", "other", "the", 0 }, 0 },
419 { 5, { "other", "the", "with", 0 }, 0 },
420 { 0, { 0, 0, 0, 0 }, 0 }
423 /// Regression test for bug fixed in 1.3.3 and 1.2.21.
424 DEFINE_TESTCASE(loosephrase1, backend) {
425 Xapian::Database db = get_database("apitest_simpledata");
426 Xapian::Enquire enq(db);
428 const positional_testcase * p = loosephrase1_testcases;
429 while (p->window) {
430 const char * const * tend = p->terms + 4;
431 while (tend[-1] == NULL) --tend;
432 Xapian::Query q(Xapian::Query::OP_PHRASE, p->terms, tend, p->window);
433 enq.set_query(q);
434 Xapian::MSet mset = enq.get_mset(0, 10);
435 if (p->result == 0) {
436 TEST(mset.empty());
437 } else {
438 TEST_EQUAL(mset.size(), 1);
439 TEST_EQUAL(*mset[0], p->result);
441 ++p;
444 return true;
447 static const
448 positional_testcase loosenear1_testcases[] = {
449 { 4, { "test", "the", "with", 0 }, 1 },
450 { 4, { "expect", "word", "the", 0 }, 2 },
451 { 4, { "line", "be", "blank", 0 }, 1 },
452 { 2, { "banana", "banana", 0, 0 }, 0 },
453 { 3, { "banana", "banana", 0, 0 }, 0 },
454 { 2, { "word", "word", 0, 0 }, 2 },
455 { 4, { "work", "meant", "work", 0 }, 0 },
456 { 4, { "this", "one", "yet", "one" }, 0 },
457 { 0, { 0, 0, 0, 0 }, 0 }
460 /// Regression tests for bugs fixed in 1.3.3 and 1.2.21.
461 DEFINE_TESTCASE(loosenear1, backend) {
462 Xapian::Database db = get_database("apitest_simpledata");
463 Xapian::Enquire enq(db);
465 const positional_testcase * p = loosenear1_testcases;
466 while (p->window) {
467 const char * const * tend = p->terms + 4;
468 while (tend[-1] == NULL) --tend;
469 Xapian::Query q(Xapian::Query::OP_NEAR, p->terms, tend, p->window);
470 enq.set_query(q);
471 Xapian::MSet mset = enq.get_mset(0, 10);
472 if (p->result == 0) {
473 TEST(mset.empty());
474 } else {
475 TEST_EQUAL(mset.size(), 1);
476 TEST_EQUAL(*mset[0], p->result);
478 ++p;
481 return true;
484 /// Regression test for bug fixed in 1.3.6 - the first case segfaulted in 1.3.x.
485 DEFINE_TESTCASE(complexphrase1, backend) {
486 Xapian::Database db = get_database("apitest_simpledata");
487 Xapian::Enquire enq(db);
488 Xapian::Query query(Xapian::Query::OP_PHRASE,
489 Xapian::Query("a") | Xapian::Query("b"),
490 Xapian::Query("i"));
491 enq.set_query(query);
492 TEST(enq.get_mset(0, 10).empty());
493 Xapian::Query query2(Xapian::Query::OP_PHRASE,
494 Xapian::Query("a") | Xapian::Query("b"),
495 Xapian::Query("c"));
496 enq.set_query(query2);
497 TEST(enq.get_mset(0, 10).empty());
498 return true;
501 /// Regression test for bug fixed in 1.3.6 - the first case segfaulted in 1.3.x.
502 DEFINE_TESTCASE(complexnear1, backend) {
503 Xapian::Database db = get_database("apitest_simpledata");
504 Xapian::Enquire enq(db);
505 Xapian::Query query(Xapian::Query::OP_NEAR,
506 Xapian::Query("a") | Xapian::Query("b"),
507 Xapian::Query("i"));
508 enq.set_query(query);
509 TEST(enq.get_mset(0, 10).empty());
510 Xapian::Query query2(Xapian::Query::OP_NEAR,
511 Xapian::Query("a") | Xapian::Query("b"),
512 Xapian::Query("c"));
513 enq.set_query(query2);
514 TEST(enq.get_mset(0, 10).empty());
515 return true;
518 /// Check subqueries of MatchAll, MatchNothing and PostingSource are supported.
519 DEFINE_TESTCASE(complexphrase2, backend) {
520 Xapian::Database db = get_database("apitest_simpledata");
521 Xapian::Enquire enq(db);
522 Xapian::ValueWeightPostingSource ps(0);
523 Xapian::Query subqs[3] = {
524 Xapian::Query(Xapian::Query::OP_PHRASE,
525 Xapian::Query("a"),
526 Xapian::Query(&ps)),
527 Xapian::Query(Xapian::Query::OP_PHRASE,
528 Xapian::Query("and"),
529 Xapian::Query::MatchAll),
530 Xapian::Query(Xapian::Query::OP_PHRASE,
531 Xapian::Query("at"),
532 Xapian::Query::MatchNothing)
534 Xapian::Query query(Xapian::Query::OP_OR, subqs, subqs + 3);
535 enq.set_query(query);
536 (void)enq.get_mset(0, 10);
537 return true;
540 /// Check subqueries of MatchAll, MatchNothing and PostingSource are supported.
541 DEFINE_TESTCASE(complexnear2, backend) {
542 Xapian::Database db = get_database("apitest_simpledata");
543 Xapian::Enquire enq(db);
544 Xapian::ValueWeightPostingSource ps(0);
545 Xapian::Query subqs[3] = {
546 Xapian::Query(Xapian::Query::OP_NEAR,
547 Xapian::Query("a"),
548 Xapian::Query(&ps)),
549 Xapian::Query(Xapian::Query::OP_NEAR,
550 Xapian::Query("and"),
551 Xapian::Query::MatchAll),
552 Xapian::Query(Xapian::Query::OP_NEAR,
553 Xapian::Query("at"),
554 Xapian::Query::MatchNothing)
556 Xapian::Query query(Xapian::Query::OP_OR, subqs, subqs + 3);
557 enq.set_query(query);
558 (void)enq.get_mset(0, 10);
559 return true;
562 /// A zero estimated number of matches broke the code to round the estimate.
563 DEFINE_TESTCASE(zeroestimate1, backend) {
564 Xapian::Enquire enquire(get_database("apitest_simpledata"));
565 Xapian::Query phrase(Xapian::Query::OP_PHRASE,
566 Xapian::Query("absolute"),
567 Xapian::Query("rubbish"));
568 enquire.set_query(phrase &~ Xapian::Query("queri"));
569 Xapian::MSet mset = enquire.get_mset(0, 0);
570 TEST_EQUAL(mset.get_matches_estimated(), 0);
571 return true;
574 /// Feature test for OR under OP_PHRASE support added in 1.4.3.
575 DEFINE_TESTCASE(complexphrase3, backend) {
576 Xapian::Database db = get_database("apitest_simpledata");
577 Xapian::Enquire enq(db);
578 Xapian::Query query(Xapian::Query::OP_PHRASE,
579 Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"),
580 Xapian::Query("a"));
581 enq.set_query(query);
582 mset_expect_order(enq.get_mset(0, 10), 1);
583 Xapian::Query query2(Xapian::Query::OP_PHRASE,
584 Xapian::Query("a"),
585 Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"));
586 enq.set_query(query2);
587 mset_expect_order(enq.get_mset(0, 10));
588 Xapian::Query query3(Xapian::Query::OP_PHRASE,
589 Xapian::Query("one") | Xapian::Query("with"),
590 Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"));
591 enq.set_query(query3);
592 mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
593 Xapian::Query query4(Xapian::Query::OP_PHRASE,
594 Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"),
595 Xapian::Query("one") | Xapian::Query("with"));
596 enq.set_query(query4);
597 mset_expect_order(enq.get_mset(0, 10));
598 return true;
601 /// Feature test for OR under OP_NEAR support added in 1.4.3.
602 DEFINE_TESTCASE(complexnear3, backend) {
603 Xapian::Database db = get_database("apitest_simpledata");
604 Xapian::Enquire enq(db);
605 Xapian::Query query(Xapian::Query::OP_NEAR,
606 Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"),
607 Xapian::Query("a"));
608 enq.set_query(query);
609 mset_expect_order(enq.get_mset(0, 10), 1);
610 Xapian::Query query2(Xapian::Query::OP_NEAR,
611 Xapian::Query("a"),
612 Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"));
613 enq.set_query(query2);
614 mset_expect_order(enq.get_mset(0, 10), 1);
615 Xapian::Query query3(Xapian::Query::OP_NEAR,
616 Xapian::Query("one") | Xapian::Query("with"),
617 Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"));
618 enq.set_query(query3);
619 mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
620 Xapian::Query query4(Xapian::Query::OP_NEAR,
621 Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"),
622 Xapian::Query("one") | Xapian::Query("with"));
623 enq.set_query(query4);
624 mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
625 return true;
628 static void
629 gen_subdbwithoutpos1_db(Xapian::WritableDatabase& db, const string&)
631 Xapian::Document doc;
632 doc.add_term("this");
633 doc.add_term("paragraph");
634 doc.add_term("wibble", 5);
635 db.add_document(doc);
638 DEFINE_TESTCASE(subdbwithoutpos1, generated) {
639 Xapian::Database db(get_database("apitest_simpledata"));
641 Xapian::Query q(Xapian::Query::OP_PHRASE,
642 Xapian::Query("this"),
643 Xapian::Query("paragraph"));
645 Xapian::Enquire enq1(db);
646 enq1.set_query(q);
647 Xapian::MSet mset1 = enq1.get_mset(0, 10);
648 TEST_EQUAL(mset1.size(), 3);
650 Xapian::Database db2 =
651 get_database("subdbwithoutpos1", gen_subdbwithoutpos1_db);
653 // If a database has no positional info, OP_PHRASE -> OP_AND.
654 Xapian::Enquire enq2(db2);
655 enq2.set_query(q);
656 Xapian::MSet mset2 = enq2.get_mset(0, 10);
657 TEST_EQUAL(mset2.size(), 1);
659 // If one sub-database in a combined database has no positional info but
660 // other sub-databases do, then we shouldn't convert OP_PHRASE to OP_AND
661 // (but prior to 1.4.3 we did).
662 db.add_database(db2);
663 Xapian::Enquire enq3(db);
664 enq3.set_query(q);
665 Xapian::MSet mset3 = enq3.get_mset(0, 10);
666 TEST_EQUAL(mset3.size(), 3);
667 // Regression test for bug introduced in 1.4.3 which led to a division by
668 // zero and then (at least on Linux) we got 1% here.
669 TEST_EQUAL(mset3[0].get_percent(), 100);
671 // Regression test for https://trac.xapian.org/ticket/752
672 enq3.set_query((Xapian::Query("this") & q) | Xapian::Query("wibble"));
673 mset3 = enq3.get_mset(0, 10);
674 TEST_EQUAL(mset3.size(), 4);
676 return true;
679 // Regression test for bug fixed in 1.4.4 and 1.2.25.
680 DEFINE_TESTCASE(notandor1, backend) {
681 Xapian::Database db(get_database("etext"));
682 Xapian::Query q =
683 Xapian::Query("the") &~ (Xapian::Query("friedrich") &
684 (Xapian::Query("day") | Xapian::Query("night")));
685 Xapian::Enquire enq(db);
686 enq.set_query(q);
688 Xapian::MSet mset = enq.get_mset(0, 10, db.get_doccount());
689 TEST_EQUAL(mset.get_matches_estimated(), 344);
691 return true;