xapian-core  1.4.19
api_query.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2008,2009,2012,2013,2015,2016,2017,2019 Olly Betts
5  *
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.
10  *
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.
15  *
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
20  */
21 
22 #include <config.h>
23 
24 #include "api_query.h"
25 
26 #include <xapian.h>
27 
28 #include "testsuite.h"
29 #include "testutils.h"
30 
31 #include "apitest.h"
32 
33 using namespace std;
34 
35 DEFINE_TESTCASE(queryterms1, !backend) {
38  TEST(query.get_terms_begin() == query.get_terms_end());
40  query = Xapian::Query(query.OP_AND_NOT, query, Xapian::Query("fair"));
41  TEST_EQUAL(*query.get_terms_begin(), "fair");
42  TEST_EQUAL(*query.get_unique_terms_begin(), "fair");
43 
45  Xapian::Query q = qp.parse_query("\"the the the\"");
46  {
47  auto t = q.get_terms_begin();
48  size_t count = 0;
49  while (t != q.get_terms_end()) {
50  TEST_EQUAL(*t, "the");
51  ++count;
52  ++t;
53  }
54  TEST_EQUAL(count, 3);
55  }
56  {
57  auto t = q.get_unique_terms_begin();
58  size_t count = 0;
59  while (t != q.get_unique_terms_end()) {
60  TEST_EQUAL(*t, "the");
61  ++count;
62  ++t;
63  }
64  TEST_EQUAL(count, 1);
65  }
66 }
67 
68 DEFINE_TESTCASE(matchall2, !backend) {
70  "Query(<alldocuments>)");
71 }
72 
73 DEFINE_TESTCASE(matchnothing1, !backend) {
75  "Query()");
76  vector<Xapian::Query> subqs;
77  subqs.push_back(Xapian::Query("foo"));
78  subqs.push_back(Xapian::Query::MatchNothing);
79  Xapian::Query q(Xapian::Query::OP_AND, subqs.begin(), subqs.end());
80  TEST_STRINGS_EQUAL(q.get_description(), "Query()");
81 
84  TEST_STRINGS_EQUAL(q2.get_description(), "Query()");
85 }
86 
87 DEFINE_TESTCASE(overload1, !backend) {
88  Xapian::Query q;
89  q = Xapian::Query("foo") & Xapian::Query("bar");
90  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND bar))");
91 
92  // Test &= appends a same-type subquery (since Xapian 1.4.10).
93  q &= Xapian::Query("baz");
94  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND bar AND baz))");
95  // But not if the RHS is the same query:
96  q = Xapian::Query("foo") & Xapian::Query("bar");
97 #ifdef __has_warning
98 # if __has_warning("-Wself-assign-overloaded")
99  // Suppress warning from newer clang about self-assignment so we can
100  // test that self-assignment works!
101 # pragma clang diagnostic push
102 # pragma clang diagnostic ignored "-Wself-assign-overloaded"
103 # endif
104 #endif
105  q &= q;
106 #ifdef __has_warning
107 # if __has_warning("-Wself-assign-overloaded")
108 # pragma clang diagnostic pop
109 # endif
110 #endif
111  TEST_STRINGS_EQUAL(q.get_description(), "Query(((foo AND bar) AND (foo AND bar)))");
112  {
113  // Also not if the query has a refcount > 1.
114  q = Xapian::Query("foo") & Xapian::Query("bar");
115  Xapian::Query qcopy = q;
116  qcopy &= Xapian::Query("baz");
117  TEST_STRINGS_EQUAL(qcopy.get_description(), "Query(((foo AND bar) AND baz))");
118  // And q shouldn't change.
119  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND bar))");
120  }
121  // Check that MatchNothing still results in MatchNothing:
122  q = Xapian::Query("foo") & Xapian::Query("bar");
124  TEST_STRINGS_EQUAL(q.get_description(), "Query()");
125  // Check we don't combine for other operators:
126  q = Xapian::Query("foo") | Xapian::Query("bar");
127  q &= Xapian::Query("baz");
128  TEST_STRINGS_EQUAL(q.get_description(), "Query(((foo OR bar) AND baz))");
129 
130  // Test |= appends a same-type subquery (since Xapian 1.4.10).
131  q = Xapian::Query("foo") | Xapian::Query("bar");
132  q |= Xapian::Query("baz");
133  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo OR bar OR baz))");
134  // But not if the RHS is the same query:
135  q = Xapian::Query("foo") | Xapian::Query("bar");
136 #ifdef __has_warning
137 # if __has_warning("-Wself-assign-overloaded")
138  // Suppress warning from newer clang about self-assignment so we can
139  // test that self-assignment works!
140 # pragma clang diagnostic push
141 # pragma clang diagnostic ignored "-Wself-assign-overloaded"
142 # endif
143 #endif
144  q |= q;
145 #ifdef __has_warning
146 # if __has_warning("-Wself-assign-overloaded")
147 # pragma clang diagnostic pop
148 # endif
149 #endif
150  TEST_STRINGS_EQUAL(q.get_description(), "Query(((foo OR bar) OR (foo OR bar)))");
151  {
152  // Also not if the query has a refcount > 1.
153  q = Xapian::Query("foo") | Xapian::Query("bar");
154  Xapian::Query qcopy = q;
155  qcopy |= Xapian::Query("baz");
156  TEST_STRINGS_EQUAL(qcopy.get_description(), "Query(((foo OR bar) OR baz))");
157  // And q shouldn't change.
158  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo OR bar))");
159  }
160  // Check that MatchNothing still results in no change:
161  q = Xapian::Query("foo") | Xapian::Query("bar");
163  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo OR bar))");
164  // Check we don't combine for other operators:
165  q = Xapian::Query("foo") & Xapian::Query("bar");
166  q |= Xapian::Query("baz");
167  TEST_STRINGS_EQUAL(q.get_description(), "Query(((foo AND bar) OR baz))");
168 
169  // Test ^= appends a same-type subquery (since Xapian 1.4.10).
170  q = Xapian::Query("foo") ^ Xapian::Query("bar");
171  q ^= Xapian::Query("baz");
172  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo XOR bar XOR baz))");
173  // But a query ^= itself gives an empty query.
174  q = Xapian::Query("foo") ^ Xapian::Query("bar");
175 #ifdef __has_warning
176 # if __has_warning("-Wself-assign-overloaded")
177  // Suppress warning from newer clang about self-assignment so we can
178  // test that self-assignment works!
179 # pragma clang diagnostic push
180 # pragma clang diagnostic ignored "-Wself-assign-overloaded"
181 # endif
182 #endif
183  q ^= q;
184 #ifdef __has_warning
185 # if __has_warning("-Wself-assign-overloaded")
186 # pragma clang diagnostic pop
187 # endif
188 #endif
189  TEST_STRINGS_EQUAL(q.get_description(), "Query()");
190  {
191  // Even if the reference count > 1.
192  q = Xapian::Query("foo") ^ Xapian::Query("bar");
193  Xapian::Query qcopy = q;
194  q ^= qcopy;
195  TEST_STRINGS_EQUAL(q.get_description(), "Query()");
196  }
197  {
198  // Also not if the query has a refcount > 1.
199  q = Xapian::Query("foo") ^ Xapian::Query("bar");
200  Xapian::Query qcopy = q;
201  qcopy ^= Xapian::Query("baz");
202  TEST_STRINGS_EQUAL(qcopy.get_description(), "Query(((foo XOR bar) XOR baz))");
203  // And q shouldn't change.
204  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo XOR bar))");
205  }
206  // Check that MatchNothing still results in no change:
207  q = Xapian::Query("foo") ^ Xapian::Query("bar");
209  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo XOR bar))");
210  // Check we don't combine for other operators:
211  q = Xapian::Query("foo") & Xapian::Query("bar");
212  q ^= Xapian::Query("baz");
213  TEST_STRINGS_EQUAL(q.get_description(), "Query(((foo AND bar) XOR baz))");
214 
215  q = Xapian::Query("foo") &~ Xapian::Query("bar");
216  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND_NOT bar))");
217  // In 1.4.9 and earlier this gave (foo AND (<alldocuments> AND_NOT bar)).
218  q = Xapian::Query("foo");
219  q &= ~Xapian::Query("bar");
220  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND_NOT bar))");
221  q = ~Xapian::Query("bar");
222  TEST_STRINGS_EQUAL(q.get_description(), "Query((<alldocuments> AND_NOT bar))");
224  TEST_STRINGS_EQUAL(q.get_description(), "Query()");
225  q = Xapian::Query("foo") | Xapian::Query("bar");
226  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo OR bar))");
228  TEST_STRINGS_EQUAL(q.get_description(), "Query(foo)");
229  q = Xapian::Query("foo") ^ Xapian::Query("bar");
230  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo XOR bar))");
232  TEST_STRINGS_EQUAL(q.get_description(), "Query(foo)");
233  q = 1.25 * (Xapian::Query("one") | Xapian::Query("two"));
234  TEST_STRINGS_EQUAL(q.get_description(), "Query(1.25 * (one OR two))");
235  q = (Xapian::Query("one") & Xapian::Query("two")) * 42;
236  TEST_STRINGS_EQUAL(q.get_description(), "Query(42 * (one AND two))");
237  q = Xapian::Query("one") / 2.0;
238  TEST_STRINGS_EQUAL(q.get_description(), "Query(0.5 * one)");
239 }
240 
248 DEFINE_TESTCASE(possubqueries1, writable) {
250  Xapian::Document doc;
251  doc.add_posting("a", 1);
252  doc.add_posting("b", 2);
253  doc.add_posting("c", 3);
254  db.add_document(doc);
255 
257  Xapian::Query("a"),
258  Xapian::Query("b"));
259  Xapian::Query near(Xapian::Query::OP_NEAR, a_or_b, a_or_b);
260  // As of 1.3.0, we no longer rearrange queries at this point, so check
261  // that we don't.
263  "Query(((a OR b) NEAR 2 (a OR b)))");
264  Xapian::Query phrase(Xapian::Query::OP_PHRASE, a_or_b, a_or_b);
265  TEST_STRINGS_EQUAL(phrase.get_description(),
266  "Query(((a OR b) PHRASE 2 (a OR b)))");
267 
269  Xapian::Query("a"),
270  Xapian::Query("b"));
272  Xapian::Query("a"),
273  Xapian::Query("b"));
275  Xapian::Query("a"),
276  Xapian::Query("b"));
277  Xapian::Query c("c");
278 
279  // FIXME: The plan is to actually try to support the cases below, but
280  // for now at least ensure they are cleanly rejected.
281  Xapian::Enquire enq(db);
282 
284  Xapian::Query q(Xapian::Query::OP_NEAR, a_and_b, c);
285  enq.set_query(q);
286  (void)enq.get_mset(0, 10));
287 
289  Xapian::Query q(Xapian::Query::OP_NEAR, a_near_b, c);
290  enq.set_query(q);
291  (void)enq.get_mset(0, 10));
292 
294  Xapian::Query q(Xapian::Query::OP_NEAR, a_phrs_b, c);
295  enq.set_query(q);
296  (void)enq.get_mset(0, 10));
297 
300  enq.set_query(q);
301  (void)enq.get_mset(0, 10));
302 
304  Xapian::Query q(Xapian::Query::OP_PHRASE, a_near_b, c);
305  enq.set_query(q);
306  (void)enq.get_mset(0, 10));
307 
309  Xapian::Query q(Xapian::Query::OP_PHRASE, a_phrs_b, c);
310  enq.set_query(q);
311  (void)enq.get_mset(0, 10));
312 }
313 
315 // time.
316 DEFINE_TESTCASE(xor3, backend) {
317  Xapian::Database db = get_database("apitest_simpledata");
318 
319  static const char * const subqs[] = {
320  "hack", "which", "paragraph", "is", "return"
321  };
322  // Document where the subqueries run out *does* match XOR:
323  Xapian::Query q(Xapian::Query::OP_XOR, subqs, subqs + 5);
324  Xapian::Enquire enq(db);
325  enq.set_query(q);
326  Xapian::MSet mset = enq.get_mset(0, 10);
327 
328  TEST_EQUAL(mset.size(), 3);
329  TEST_EQUAL(*mset[0], 4);
330  TEST_EQUAL(*mset[1], 2);
331  TEST_EQUAL(*mset[2], 3);
332 
333  // Document where the subqueries run out *does not* match XOR:
334  q = Xapian::Query(Xapian::Query::OP_XOR, subqs, subqs + 4);
335  enq.set_query(q);
336  mset = enq.get_mset(0, 10);
337 
338  TEST_EQUAL(mset.size(), 4);
339  TEST_EQUAL(*mset[0], 5);
340  TEST_EQUAL(*mset[1], 4);
341  TEST_EQUAL(*mset[2], 2);
342  TEST_EQUAL(*mset[3], 3);
343 }
344 
346 DEFINE_TESTCASE(nonutf8termdesc1, !backend) {
347  TEST_EQUAL(Xapian::Query("\xc0\x80\xf5\x80\x80\x80\xfe\xff").get_description(),
348  "Query(\\xc0\\x80\\xf5\\x80\\x80\\x80\\xfe\\xff)");
349  TEST_EQUAL(Xapian::Query(string("\x00\x1f", 2)).get_description(),
350  "Query(\\x00\\x1f)");
351  // Check that backslashes are encoded so output isn't ambiguous.
352  TEST_EQUAL(Xapian::Query("back\\slash").get_description(),
353  "Query(back\\x5cslash)");
354  // Check that \x7f is escaped.
355  TEST_EQUAL(Xapian::Query("D\x7f_\x7f~").get_description(),
356  "Query(D\\x7f_\\x7f~)");
357 }
358 
360 DEFINE_TESTCASE(queryintro1, !backend) {
362  TEST_EQUAL(Xapian::Query::MatchAll.get_num_subqueries(), 0);
364  TEST_EQUAL(Xapian::Query::MatchNothing.get_num_subqueries(), 0);
365 
366  Xapian::Query q;
372 
373  q = Xapian::Query("foo") & Xapian::Query("bar");
374  TEST_EQUAL(q.get_type(), q.OP_AND);
375 
376  q = Xapian::Query("foo") &~ Xapian::Query("bar");
377  TEST_EQUAL(q.get_type(), q.OP_AND_NOT);
378 
379  q = ~Xapian::Query("bar");
380  TEST_EQUAL(q.get_type(), q.OP_AND_NOT);
381 
382  q = Xapian::Query("foo") | Xapian::Query("bar");
383  TEST_EQUAL(q.get_type(), q.OP_OR);
384 
385  q = Xapian::Query("foo") ^ Xapian::Query("bar");
386  TEST_EQUAL(q.get_type(), q.OP_XOR);
387 
388  q = 1.25 * (Xapian::Query("one") | Xapian::Query("two"));
389  TEST_EQUAL(q.get_type(), q.OP_SCALE_WEIGHT);
390  TEST_EQUAL(q.get_num_subqueries(), 1);
391  TEST_EQUAL(q.get_subquery(0).get_type(), q.OP_OR);
392 
393  q = Xapian::Query("one") / 2.0;
394  TEST_EQUAL(q.get_type(), q.OP_SCALE_WEIGHT);
395  TEST_EQUAL(q.get_num_subqueries(), 1);
396  TEST_EQUAL(q.get_subquery(0).get_type(), q.LEAF_TERM);
397 
398  q = Xapian::Query(q.OP_NEAR, Xapian::Query("a"), Xapian::Query("b"));
399  TEST_EQUAL(q.get_type(), q.OP_NEAR);
400  TEST_EQUAL(q.get_num_subqueries(), 2);
401  TEST_EQUAL(q.get_subquery(0).get_type(), q.LEAF_TERM);
402  TEST_EQUAL(q.get_subquery(1).get_type(), q.LEAF_TERM);
403 
404  q = Xapian::Query(q.OP_PHRASE, Xapian::Query("c"), Xapian::Query("d"));
405  TEST_EQUAL(q.get_type(), q.OP_PHRASE);
406  TEST_EQUAL(q.get_num_subqueries(), 2);
407  TEST_EQUAL(q.get_subquery(0).get_type(), q.LEAF_TERM);
408  TEST_EQUAL(q.get_subquery(1).get_type(), q.LEAF_TERM);
409 }
410 
412 // We were incorrectly converting a term which indexed all docs and was used
413 // in an unweighted phrase into an all docs postlist, so check that this
414 // case actually works.
415 DEFINE_TESTCASE(phrasealldocs1, backend) {
416  Xapian::Database db = get_database("apitest_declen");
417  Xapian::Query q;
418  static const char * const phrase[] = { "this", "is", "the" };
420  Xapian::Query("paragraph"),
421  Xapian::Query(q.OP_PHRASE, phrase, phrase + 3));
422  Xapian::Enquire enq(db);
423  enq.set_query(q);
424  Xapian::MSet mset = enq.get_mset(0, 10);
425  TEST_EQUAL(mset.size(), 3);
426 }
427 
429  const char * pattern;
431  char max_type;
432  const char * terms[4];
433 };
434 
435 #define WILDCARD_EXCEPTION { 0, 0, 0, "" }
436 static const
438  // Tries to expand to 7 terms.
439  { "th", 6, 'E', WILDCARD_EXCEPTION },
440  { "thou", 1, 'E', { "though", 0, 0, 0 } },
441  { "s", 2, 'F', { "say", "search", 0, 0 } },
442  { "s", 2, 'M', { "simpl", "so", 0, 0 } }
443 };
444 
445 DEFINE_TESTCASE(wildcard1, backend) {
446  // FIXME: The counting of terms the wildcard expands to is per subdatabase,
447  // so the wildcard may expand to more terms than the limit if some aren't
448  // in all subdatabases. Also WILDCARD_LIMIT_MOST_FREQUENT uses the
449  // frequency from the subdatabase, and so may select different terms in
450  // each subdatabase.
451  SKIP_TEST_FOR_BACKEND("multi");
452  Xapian::Database db = get_database("apitest_simpledata");
453  Xapian::Enquire enq(db);
455 
456  for (auto&& test : wildcard1_testcases) {
457  tout << test.pattern << endl;
458  auto tend = test.terms + 4;
459  while (tend[-1] == NULL) --tend;
460  bool expect_exception = (tend - test.terms == 4 && tend[-1][0] == '\0');
461  Xapian::Query q;
462  if (test.max_type) {
463  int max_type;
464  switch (test.max_type) {
465  case 'E':
467  break;
468  case 'F':
470  break;
471  case 'M':
473  break;
474  default:
475  FAIL_TEST("Unexpected max_type value");
476  }
477  q = Xapian::Query(o, test.pattern, test.max_expansion, max_type);
478  } else {
479  q = Xapian::Query(o, test.pattern, test.max_expansion);
480  }
481  enq.set_query(q);
482  try {
483  Xapian::MSet mset = enq.get_mset(0, 10);
484  TEST(!expect_exception);
485  q = Xapian::Query(q.OP_SYNONYM, test.terms, tend);
486  enq.set_query(q);
487  Xapian::MSet mset2 = enq.get_mset(0, 10);
488  TEST_EQUAL(mset.size(), mset2.size());
489  TEST(mset_range_is_same(mset, 0, mset2, 0, mset.size()));
490  } catch (const Xapian::WildcardError &) {
491  TEST(expect_exception);
492  }
493  }
494 }
495 
497 DEFINE_TESTCASE(wildcard2, backend) {
498  // FIXME: The counting of terms the wildcard expands to is per subdatabase,
499  // so the wildcard may expand to more terms than the limit if some aren't
500  // in all subdatabases. Also WILDCARD_LIMIT_MOST_FREQUENT uses the
501  // frequency from the subdatabase, and so may select different terms in
502  // each subdatabase.
503  SKIP_TEST_FOR_BACKEND("multi");
504  Xapian::Database db = get_database("apitest_simpledata");
505  Xapian::Enquire enq(db);
507 
508  const int max_type = Xapian::Query::WILDCARD_LIMIT_MOST_FREQUENT;
509  Xapian::Query q0(o, "w", 2, max_type);
510  Xapian::Query q(o, "s", 2, max_type);
511  Xapian::Query q2(o, "t", 2, max_type);
512  q = Xapian::Query(q.OP_OR, q0, q);
513  q = Xapian::Query(q.OP_OR, q, q2);
514  enq.set_query(q);
515  Xapian::MSet mset = enq.get_mset(0, 10);
516  TEST_EQUAL(mset.size(), 6);
517 }
518 
519 DEFINE_TESTCASE(dualprefixwildcard1, backend) {
520  Xapian::Database db = get_database("apitest_simpledata");
524  tout << q.get_description() << endl;
525  Xapian::Enquire enq(db);
526  enq.set_query(q);
527  TEST_EQUAL(enq.get_mset(0, 5).size(), 2);
528 }
529 
531  int window;
532  const char * terms[4];
534 };
535 
536 static const
538  { 5, { "expect", "to", "mset", 0 }, 0 },
539  { 5, { "word", "well", "the", 0 }, 2 },
540  { 5, { "if", "word", "doesnt", 0 }, 0 },
541  { 5, { "at", "line", "three", 0 }, 0 },
542  { 5, { "paragraph", "other", "the", 0 }, 0 },
543  { 5, { "other", "the", "with", 0 }, 0 }
544 };
545 
547 DEFINE_TESTCASE(loosephrase1, backend) {
548  Xapian::Database db = get_database("apitest_simpledata");
549  Xapian::Enquire enq(db);
550 
551  for (auto&& test : loosephrase1_testcases) {
552  auto tend = test.terms + 4;
553  while (tend[-1] == NULL) --tend;
554  auto OP_PHRASE = Xapian::Query::OP_PHRASE;
555  Xapian::Query q(OP_PHRASE, test.terms, tend, test.window);
556  enq.set_query(q);
557  Xapian::MSet mset = enq.get_mset(0, 10);
558  if (test.result == 0) {
559  TEST(mset.empty());
560  } else {
561  TEST_EQUAL(mset.size(), 1);
562  TEST_EQUAL(*mset[0], test.result);
563  }
564  }
565 }
566 
567 static const
569  { 4, { "test", "the", "with", 0 }, 1 },
570  { 4, { "expect", "word", "the", 0 }, 2 },
571  { 4, { "line", "be", "blank", 0 }, 1 },
572  { 2, { "banana", "banana", 0, 0 }, 0 },
573  { 3, { "banana", "banana", 0, 0 }, 0 },
574  { 2, { "word", "word", 0, 0 }, 2 },
575  { 4, { "work", "meant", "work", 0 }, 0 },
576  { 4, { "this", "one", "yet", "one" }, 0 }
577 };
578 
580 DEFINE_TESTCASE(loosenear1, backend) {
581  Xapian::Database db = get_database("apitest_simpledata");
582  Xapian::Enquire enq(db);
583 
584  for (auto&& test : loosenear1_testcases) {
585  auto tend = test.terms + 4;
586  while (tend[-1] == NULL) --tend;
587  Xapian::Query q(Xapian::Query::OP_NEAR, test.terms, tend, test.window);
588  enq.set_query(q);
589  Xapian::MSet mset = enq.get_mset(0, 10);
590  if (test.result == 0) {
591  TEST(mset.empty());
592  } else {
593  TEST_EQUAL(mset.size(), 1);
594  TEST_EQUAL(*mset[0], test.result);
595  }
596  }
597 }
598 
600 DEFINE_TESTCASE(complexphrase1, backend) {
601  Xapian::Database db = get_database("apitest_simpledata");
602  Xapian::Enquire enq(db);
604  Xapian::Query("a") | Xapian::Query("b"),
605  Xapian::Query("i"));
606  enq.set_query(query);
607  TEST(enq.get_mset(0, 10).empty());
609  Xapian::Query("a") | Xapian::Query("b"),
610  Xapian::Query("c"));
611  enq.set_query(query2);
612  TEST(enq.get_mset(0, 10).empty());
613 }
614 
616 DEFINE_TESTCASE(complexnear1, backend) {
617  Xapian::Database db = get_database("apitest_simpledata");
618  Xapian::Enquire enq(db);
620  Xapian::Query("a") | Xapian::Query("b"),
621  Xapian::Query("i"));
622  enq.set_query(query);
623  TEST(enq.get_mset(0, 10).empty());
625  Xapian::Query("a") | Xapian::Query("b"),
626  Xapian::Query("c"));
627  enq.set_query(query2);
628  TEST(enq.get_mset(0, 10).empty());
629 }
630 
632 DEFINE_TESTCASE(complexphrase2, backend) {
633  Xapian::Database db = get_database("apitest_simpledata");
634  Xapian::Enquire enq(db);
636  Xapian::Query subqs[3] = {
638  Xapian::Query("a"),
639  Xapian::Query(&ps)),
641  Xapian::Query("and"),
644  Xapian::Query("at"),
646  };
647  Xapian::Query query(Xapian::Query::OP_OR, subqs, subqs + 3);
648  enq.set_query(query);
649  (void)enq.get_mset(0, 10);
650 }
651 
653 DEFINE_TESTCASE(complexnear2, backend) {
654  Xapian::Database db = get_database("apitest_simpledata");
655  Xapian::Enquire enq(db);
657  Xapian::Query subqs[3] = {
659  Xapian::Query("a"),
660  Xapian::Query(&ps)),
662  Xapian::Query("and"),
665  Xapian::Query("at"),
667  };
668  Xapian::Query query(Xapian::Query::OP_OR, subqs, subqs + 3);
669  enq.set_query(query);
670  (void)enq.get_mset(0, 10);
671 }
672 
674 DEFINE_TESTCASE(zeroestimate1, backend) {
675  Xapian::Enquire enquire(get_database("apitest_simpledata"));
677  Xapian::Query("absolute"),
678  Xapian::Query("rubbish"));
679  enquire.set_query(phrase &~ Xapian::Query("queri"));
680  Xapian::MSet mset = enquire.get_mset(0, 0);
682 }
683 
685 DEFINE_TESTCASE(complexphrase3, backend) {
686  Xapian::Database db = get_database("apitest_simpledata");
687  Xapian::Enquire enq(db);
689  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"),
690  Xapian::Query("a"));
691  enq.set_query(query);
692  mset_expect_order(enq.get_mset(0, 10), 1);
694  Xapian::Query("a"),
695  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"));
696  enq.set_query(query2);
697  mset_expect_order(enq.get_mset(0, 10));
699  Xapian::Query("one") | Xapian::Query("with"),
700  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"));
701  enq.set_query(query3);
702  mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
704  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"),
705  Xapian::Query("one") | Xapian::Query("with"));
706  enq.set_query(query4);
707  mset_expect_order(enq.get_mset(0, 10));
708 }
709 
711 DEFINE_TESTCASE(complexnear3, backend) {
712  Xapian::Database db = get_database("apitest_simpledata");
713  Xapian::Enquire enq(db);
715  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"),
716  Xapian::Query("a"));
717  enq.set_query(query);
718  mset_expect_order(enq.get_mset(0, 10), 1);
720  Xapian::Query("a"),
721  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"));
722  enq.set_query(query2);
723  mset_expect_order(enq.get_mset(0, 10), 1);
725  Xapian::Query("one") | Xapian::Query("with"),
726  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"));
727  enq.set_query(query3);
728  mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
730  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"),
731  Xapian::Query("one") | Xapian::Query("with"));
732  enq.set_query(query4);
733  mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
734 }
735 
736 static void
738 {
739  Xapian::Document doc;
740  doc.add_term("this");
741  doc.add_term("paragraph");
742  doc.add_term("wibble", 5);
743  db.add_document(doc);
744 }
745 
746 DEFINE_TESTCASE(subdbwithoutpos1, generated) {
747  XFAIL_FOR_BACKEND("remote",
748  "Known but obscure remote bug which doesn't justify "
749  "protocol version bump");
750  XFAIL_FOR_BACKEND("multi_remote",
751  "Known but obscure remote bug which doesn't justify "
752  "protocol version bump");
753 
754  Xapian::Database db(get_database("apitest_simpledata"));
755 
757  Xapian::Query("this"),
758  Xapian::Query("paragraph"));
759 
760  Xapian::Enquire enq1(db);
761  enq1.set_query(q);
762  Xapian::MSet mset1 = enq1.get_mset(0, 10);
763  TEST_EQUAL(mset1.size(), 3);
764 
765  Xapian::Database db2 =
766  get_database("subdbwithoutpos1", gen_subdbwithoutpos1_db);
767 
768  // If a database has no positional info, OP_PHRASE -> OP_AND.
769  Xapian::Enquire enq2(db2);
770  enq2.set_query(q);
771  Xapian::MSet mset2 = enq2.get_mset(0, 10);
772  TEST_EQUAL(mset2.size(), 1);
773 
774  // If one sub-database in a combined database has no positional info but
775  // other sub-databases do, then we shouldn't convert OP_PHRASE to OP_AND
776  // (but prior to 1.4.3 we did).
777  db.add_database(db2);
778  Xapian::Enquire enq3(db);
779  enq3.set_query(q);
780  Xapian::MSet mset3 = enq3.get_mset(0, 10);
781  TEST_EQUAL(mset3.size(), 3);
782  // Regression test for bug introduced in 1.4.3 which led to a division by
783  // zero and then (at least on Linux) we got 1% here.
784  TEST_EQUAL(mset3[0].get_percent(), 100);
785 
786  // Regression test for https://trac.xapian.org/ticket/752
787  enq3.set_query((Xapian::Query("this") & q) | Xapian::Query("wibble"));
788  mset3 = enq3.get_mset(0, 10);
789  TEST_EQUAL(mset3.size(), 4);
790 }
791 
792 // Regression test for bug fixed in 1.4.4 and 1.2.25.
793 DEFINE_TESTCASE(notandor1, backend) {
794  Xapian::Database db(get_database("etext"));
795  using Xapian::Query;
796  Query q = Query("the") &~ (Query("friedrich") &
797  (Query("day") | Query("night")));
798  Xapian::Enquire enq(db);
799  enq.set_query(q);
800 
801  Xapian::MSet mset = enq.get_mset(0, 10, db.get_doccount());
802  TEST_EQUAL(mset.get_matches_estimated(), 344);
803 }
804 
805 // Regression test for bug fixed in git master before 1.5.0.
806 DEFINE_TESTCASE(boolorbug1, backend) {
807  Xapian::Database db(get_database("etext"));
808  using Xapian::Query;
809  Query q = Query("the") &~ Query(Query::OP_WILDCARD, "pru");
810  Xapian::Enquire enq(db);
811  enq.set_query(q);
812 
813  Xapian::MSet mset = enq.get_mset(0, 10, db.get_doccount());
814  // Due to a bug in BoolOrPostList this returned 330 results.
815  TEST_EQUAL(mset.get_matches_estimated(), 331);
816 }
817 
818 // Regression test for bug introduced in 1.4.13 and fixed in 1.4.14.
819 DEFINE_TESTCASE(hoistnotbug1, backend) {
820  Xapian::Database db(get_database("etext"));
821  using Xapian::Query;
822  Query q(Query::OP_PHRASE, Query("the"), Query("king"));
823  q &= ~Query("worldtornado");
824  q &= Query("a");
825  Xapian::Enquire enq(db);
826  enq.set_query(q);
827 
828  // This reliably fails before the fix in an assertion build, and may crash
829  // in other builds.
830  Xapian::MSet mset = enq.get_mset(0, 10, db.get_doccount());
831  TEST_EQUAL(mset.get_matches_estimated(), 42);
832 }
833 
834 // Regression test for segfault optimising query on git master before 1.5.0.
835 DEFINE_TESTCASE(emptynot1, backend) {
836  Xapian::Database db(get_database("apitest_simpledata"));
837  Xapian::Enquire enq(db);
839  Xapian::Query query = Xapian::Query("document") & Xapian::Query("api");
840  // This range won't match anything, so collapses to MatchNothing as we
841  // optimise the query.
842  query = Xapian::Query(query.OP_AND_NOT,
843  query,
845  enq.set_query(query);
846  Xapian::MSet mset = enq.get_mset(0, 10);
847  TEST_EQUAL(mset.size(), 1);
848 }
849 
850 // Similar case to emptynot1 but for OP_AND_MAYBE. This case wasn't failing,
851 // so this isn't a regression test, but we do want to ensure it works.
852 DEFINE_TESTCASE(emptymaybe1, backend) {
853  Xapian::Database db(get_database("apitest_simpledata"));
854  Xapian::Enquire enq(db);
856  Xapian::Query query = Xapian::Query("document") & Xapian::Query("api");
857  // This range won't match anything, so collapses to MatchNothing as we
858  // optimise the query.
859  query = Xapian::Query(query.OP_AND_MAYBE,
860  query,
862  enq.set_query(query);
863  Xapian::MSet mset = enq.get_mset(0, 10);
864  TEST_EQUAL(mset.size(), 1);
865 }
866 
867 DEFINE_TESTCASE(phraseweightcheckbug1, backend) {
868  Xapian::Database db(get_database("phraseweightcheckbug1"));
869  Xapian::Enquire enq(db);
870  static const char* const words[] = {"hello", "world"};
871  Xapian::Query query{Xapian::Query::OP_PHRASE, begin(words), end(words), 2};
873  tout << query.get_description() << '\n';
874  enq.set_query(query);
875  Xapian::MSet mset = enq.get_mset(0, 3);
876  TEST_EQUAL(mset.size(), 3);
877 }
878 
879 DEFINE_TESTCASE(orphanedhint1, backend) {
880  Xapian::Database db(get_database("apitest_simpledata"));
881  Xapian::Enquire enq(db);
882  auto OP_WILDCARD = Xapian::Query::OP_WILDCARD;
883  Xapian::Query query = Xapian::Query(OP_WILDCARD, "doc") &
884  Xapian::Query(OP_WILDCARD, "xyzzy");
885  query |= Xapian::Query("test");
886  tout << query.get_description() << '\n';
887  enq.set_query(query);
888  Xapian::MSet mset = enq.get_mset(0, 3);
889  TEST_EQUAL(mset.size(), 1);
890 }
const TermIterator get_unique_terms_end() const
End iterator for unique terms in the query object.
Definition: query.h:516
The Xapian namespace contains public interfaces for the Xapian library.
Definition: compactor.cc:80
Xapian::doccount size() const
Return number of items in this MSet object.
Definition: omenquire.cc:318
Xapian::docid add_document(const Xapian::Document &document)
Add a new document to the database.
Definition: omdatabase.cc:902
Wildcard expansion.
Definition: query.h:255
const Query get_subquery(size_t n) const
Read a top level subquery.
Definition: query.cc:226
Xapian::termcount max_expansion
Definition: api_query.cc:430
#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
static const Xapian::Query MatchAll
A query matching all documents.
Definition: query.h:75
Match documents which an odd number of subqueries match.
Definition: query.h:107
const TermIterator get_terms_begin() const
Begin iterator for terms in the query object.
Definition: query.cc:135
bool mset_range_is_same(const Xapian::MSet &mset1, unsigned int first1, const Xapian::MSet &mset2, unsigned int first2, unsigned int count)
Definition: testutils.cc:46
op
Query operators.
Definition: query.h:78
bool empty() const
Return true if this MSet object is empty.
Definition: mset.h:283
Xapian::WritableDatabase get_writable_database(const string &dbname)
Definition: apitest.cc:87
Build a Xapian::Query object from a user query string.
Definition: queryparser.h:778
WildcardError indicates an error expanding a wildcarded query.
Definition: error.h:1013
a generic test suite engine
Class representing a list of search results.
Definition: mset.h:44
STL namespace.
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
Xapian::doccount get_doccount() const
Get the number of documents in the database.
Definition: omdatabase.cc:267
const TermIterator get_unique_terms_begin() const
Begin iterator for unique terms in the query object.
Definition: query.cc:160
#define WILDCARD_EXCEPTION
Definition: api_query.cc:435
const char * pattern
Definition: api_query.cc:429
test functionality of the Xapian API
unsigned XAPIAN_TERMCOUNT_BASE_TYPE termcount
A counts of terms.
Definition: types.h:72
Class implementing a "boolean" weighting scheme.
Definition: weight.h:422
Limit OP_WILDCARD expansion to the most frequent terms.
Definition: query.h:307
static const wildcard_testcase wildcard1_testcases[]
Definition: api_query.cc:437
This class provides read/write access to a database.
Definition: database.h:785
Value returned by get_type() for MatchAll or equivalent.
Definition: query.h:276
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
Match the first subquery taking extra weight from other subqueries.
Definition: query.h:118
Public interfaces for the Xapian library.
Match only documents where a value slot is >= a given value.
Definition: query.h:223
#define TEST_EXCEPTION(TYPE, CODE)
Check that CODE throws exactly Xapian exception TYPE.
Definition: testutils.h:109
static const positional_testcase loosenear1_testcases[]
Definition: api_query.cc:568
static void gen_subdbwithoutpos1_db(Xapian::WritableDatabase &db, const string &)
Definition: api_query.cc:737
Throw an error if OP_WILDCARD exceeds its expansion limit.
Definition: query.h:291
static const positional_testcase loosephrase1_testcases[]
Definition: api_query.cc:537
Xapian::docid result
Definition: api_query.cc:533
#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
DEFINE_TESTCASE(queryterms1, !backend)
Definition: api_query.cc:35
Match like OP_OR but weighting as if a single term.
Definition: query.h:239
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
A posting source which reads weights from a value slot.
#define FAIL_TEST(MSG)
Fail the current testcase with message MSG.
Definition: testsuite.h:68
Match only documents which all subqueries match.
Definition: query.h:84
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
Xapian::doccount get_matches_estimated() const
Estimate of the total number of matching documents.
Definition: omenquire.cc:253
size_t get_num_subqueries() const
Get the number of subqueries of the top level query.
Definition: query.cc:220
void XFAIL_FOR_BACKEND(const std::string &backend_prefix, const char *msg)
Definition: apitest.cc:147
std::string get_description() const
Return a string describing this object.
Definition: query.cc:232
This class provides an interface to the information retrieval system for the purpose of searching...
Definition: enquire.h:152
Match only documents where all subqueries match near each other.
Definition: query.h:140
Value returned by get_type() for a term.
Definition: query.h:266
Match documents which the first subquery matches but no others do.
Definition: query.h:99
Match documents which at least one subquery matches.
Definition: query.h:92
Xapian-specific test helper functions and macros.
static const Xapian::Query MatchNothing
A query matching no documents.
Definition: query.h:65
#define TEST_STRINGS_EQUAL(a, b)
Test for equality of two strings.
Definition: testsuite.h:287
Stop expanding when OP_WILDCARD reaches its expansion limit.
Definition: query.h:297
op get_type() const
Get the type of the top level of the query.
Definition: query.cc:212
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
void set_weighting_scheme(const Weight &weight_)
Set the weighting scheme to use for queries.
Definition: omenquire.cc:819
unsigned XAPIAN_DOCID_BASE_TYPE docid
A unique identifier for a document.
Definition: types.h:52
Class representing a query.
Definition: query.h:46
#define TEST_EQUAL(a, b)
Test for equality of two things.
Definition: testsuite.h:278
const TermIterator get_terms_end() const
End iterator for terms in the query object.
Definition: query.h:502
A handle representing a document in a Xapian database.
Definition: document.h:61
UnimplementedError indicates an attempt to use an unimplemented feature.
Definition: error.h:325
void add_term(const std::string &tname, Xapian::termcount wdfinc=1)
Add a term to the document, without positional information.
Definition: omdocument.cc:140
Value returned by get_type() for MatchNothing or equivalent.
Definition: query.h:282