xapian-core  1.4.27
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 
88  TEST_STRINGS_EQUAL(q2.get_description(), "Query()");
89 
92  TEST_STRINGS_EQUAL(q4.get_description(), "Query(foo)");
93 
96  TEST_STRINGS_EQUAL(q5.get_description(), "Query()");
97 
100  TEST_STRINGS_EQUAL(q6.get_description(), "Query(foo)");
101 
104  TEST_STRINGS_EQUAL(q7.get_description(), "Query()");
105 }
106 
107 DEFINE_TESTCASE(overload1, !backend) {
108  Xapian::Query q;
109  q = Xapian::Query("foo") & Xapian::Query("bar");
110  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND bar))");
111 
112  // Test &= appends a same-type subquery (since Xapian 1.4.10).
113  q &= Xapian::Query("baz");
114  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND bar AND baz))");
115  // But not if the RHS is the same query:
116  q = Xapian::Query("foo") & Xapian::Query("bar");
117 #ifdef __has_warning
118 # if __has_warning("-Wself-assign-overloaded")
119  // Suppress warning from newer clang about self-assignment so we can
120  // test that self-assignment works!
121 # pragma clang diagnostic push
122 # pragma clang diagnostic ignored "-Wself-assign-overloaded"
123 # endif
124 #endif
125  q &= q;
126 #ifdef __has_warning
127 # if __has_warning("-Wself-assign-overloaded")
128 # pragma clang diagnostic pop
129 # endif
130 #endif
131  TEST_STRINGS_EQUAL(q.get_description(), "Query(((foo AND bar) AND (foo AND bar)))");
132  {
133  // Also not if the query has a refcount > 1.
134  q = Xapian::Query("foo") & Xapian::Query("bar");
135  Xapian::Query qcopy = q;
136  qcopy &= Xapian::Query("baz");
137  TEST_STRINGS_EQUAL(qcopy.get_description(), "Query(((foo AND bar) AND baz))");
138  // And q shouldn't change.
139  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND bar))");
140  }
141  // Check that MatchNothing still results in MatchNothing:
142  q = Xapian::Query("foo") & Xapian::Query("bar");
144  TEST_STRINGS_EQUAL(q.get_description(), "Query()");
145  // Check we don't combine for other operators:
146  q = Xapian::Query("foo") | Xapian::Query("bar");
147  q &= Xapian::Query("baz");
148  TEST_STRINGS_EQUAL(q.get_description(), "Query(((foo OR bar) AND baz))");
149 
150  // Test |= appends a same-type subquery (since Xapian 1.4.10).
151  q = Xapian::Query("foo") | Xapian::Query("bar");
152  q |= Xapian::Query("baz");
153  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo OR bar OR baz))");
154  // But not if the RHS is the same query:
155  q = Xapian::Query("foo") | Xapian::Query("bar");
156 #ifdef __has_warning
157 # if __has_warning("-Wself-assign-overloaded")
158  // Suppress warning from newer clang about self-assignment so we can
159  // test that self-assignment works!
160 # pragma clang diagnostic push
161 # pragma clang diagnostic ignored "-Wself-assign-overloaded"
162 # endif
163 #endif
164  q |= q;
165 #ifdef __has_warning
166 # if __has_warning("-Wself-assign-overloaded")
167 # pragma clang diagnostic pop
168 # endif
169 #endif
170  TEST_STRINGS_EQUAL(q.get_description(), "Query(((foo OR bar) OR (foo OR bar)))");
171  {
172  // Also not if the query has a refcount > 1.
173  q = Xapian::Query("foo") | Xapian::Query("bar");
174  Xapian::Query qcopy = q;
175  qcopy |= Xapian::Query("baz");
176  TEST_STRINGS_EQUAL(qcopy.get_description(), "Query(((foo OR bar) OR baz))");
177  // And q shouldn't change.
178  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo OR bar))");
179  }
180  // Check that MatchNothing still results in no change:
181  q = Xapian::Query("foo") | Xapian::Query("bar");
183  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo OR bar))");
184  // Check we don't combine for other operators:
185  q = Xapian::Query("foo") & Xapian::Query("bar");
186  q |= Xapian::Query("baz");
187  TEST_STRINGS_EQUAL(q.get_description(), "Query(((foo AND bar) OR baz))");
188 
189  // Test ^= appends a same-type subquery (since Xapian 1.4.10).
190  q = Xapian::Query("foo") ^ Xapian::Query("bar");
191  q ^= Xapian::Query("baz");
192  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo XOR bar XOR baz))");
193  // But a query ^= itself gives an empty query.
194  q = Xapian::Query("foo") ^ Xapian::Query("bar");
195 #ifdef __has_warning
196 # if __has_warning("-Wself-assign-overloaded")
197  // Suppress warning from newer clang about self-assignment so we can
198  // test that self-assignment works!
199 # pragma clang diagnostic push
200 # pragma clang diagnostic ignored "-Wself-assign-overloaded"
201 # endif
202 #endif
203  q ^= q;
204 #ifdef __has_warning
205 # if __has_warning("-Wself-assign-overloaded")
206 # pragma clang diagnostic pop
207 # endif
208 #endif
209  TEST_STRINGS_EQUAL(q.get_description(), "Query()");
210  {
211  // Even if the reference count > 1.
212  q = Xapian::Query("foo") ^ Xapian::Query("bar");
213  Xapian::Query qcopy = q;
214  q ^= qcopy;
215  TEST_STRINGS_EQUAL(q.get_description(), "Query()");
216  }
217  {
218  // Also not if the query has a refcount > 1.
219  q = Xapian::Query("foo") ^ Xapian::Query("bar");
220  Xapian::Query qcopy = q;
221  qcopy ^= Xapian::Query("baz");
222  TEST_STRINGS_EQUAL(qcopy.get_description(), "Query(((foo XOR bar) XOR baz))");
223  // And q shouldn't change.
224  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo XOR bar))");
225  }
226  // Check that MatchNothing still results in no change:
227  q = Xapian::Query("foo") ^ Xapian::Query("bar");
229  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo XOR bar))");
230  // Check we don't combine for other operators:
231  q = Xapian::Query("foo") & Xapian::Query("bar");
232  q ^= Xapian::Query("baz");
233  TEST_STRINGS_EQUAL(q.get_description(), "Query(((foo AND bar) XOR baz))");
234 
235  q = Xapian::Query("foo") &~ Xapian::Query("bar");
236  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND_NOT bar))");
237  // In 1.4.9 and earlier this gave (foo AND (<alldocuments> AND_NOT bar)).
238  q = Xapian::Query("foo");
239  q &= ~Xapian::Query("bar");
240  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo AND_NOT bar))");
241  q = ~Xapian::Query("bar");
242  TEST_STRINGS_EQUAL(q.get_description(), "Query((<alldocuments> AND_NOT bar))");
244  TEST_STRINGS_EQUAL(q.get_description(), "Query()");
245  q = Xapian::Query("foo") | Xapian::Query("bar");
246  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo OR bar))");
248  TEST_STRINGS_EQUAL(q.get_description(), "Query(foo)");
249  q = Xapian::Query("foo") ^ Xapian::Query("bar");
250  TEST_STRINGS_EQUAL(q.get_description(), "Query((foo XOR bar))");
252  TEST_STRINGS_EQUAL(q.get_description(), "Query(foo)");
253  q = 1.25 * (Xapian::Query("one") | Xapian::Query("two"));
254  TEST_STRINGS_EQUAL(q.get_description(), "Query(1.25 * (one OR two))");
255  q = (Xapian::Query("one") & Xapian::Query("two")) * 42;
256  TEST_STRINGS_EQUAL(q.get_description(), "Query(42 * (one AND two))");
257  q = Xapian::Query("one") / 2.0;
258  TEST_STRINGS_EQUAL(q.get_description(), "Query(0.5 * one)");
259 }
260 
268 DEFINE_TESTCASE(possubqueries1, backend) {
269  Xapian::Database db = get_database("possubqueries1",
270  [](Xapian::WritableDatabase& wdb,
271  const string&)
272  {
273  Xapian::Document doc;
274  doc.add_posting("a", 1);
275  doc.add_posting("b", 2);
276  doc.add_posting("c", 3);
277  wdb.add_document(doc);
278  });
279 
281  Xapian::Query("a"),
282  Xapian::Query("b"));
283  Xapian::Query near(Xapian::Query::OP_NEAR, a_or_b, a_or_b);
284  // As of 1.3.0, we no longer rearrange queries at this point, so check
285  // that we don't.
287  "Query(((a OR b) NEAR 2 (a OR b)))");
288  Xapian::Query phrase(Xapian::Query::OP_PHRASE, a_or_b, a_or_b);
289  TEST_STRINGS_EQUAL(phrase.get_description(),
290  "Query(((a OR b) PHRASE 2 (a OR b)))");
291 
293  Xapian::Query("a"),
294  Xapian::Query("b"));
296  Xapian::Query("a"),
297  Xapian::Query("b"));
299  Xapian::Query("a"),
300  Xapian::Query("b"));
301  Xapian::Query c("c");
302 
303  // FIXME: The plan is to actually try to support the cases below, but
304  // for now at least ensure they are cleanly rejected.
305  Xapian::Enquire enq(db);
306 
308  Xapian::Query q(Xapian::Query::OP_NEAR, a_and_b, c);
309  enq.set_query(q);
310  (void)enq.get_mset(0, 10));
311 
313  Xapian::Query q(Xapian::Query::OP_NEAR, a_near_b, c);
314  enq.set_query(q);
315  (void)enq.get_mset(0, 10));
316 
318  Xapian::Query q(Xapian::Query::OP_NEAR, a_phrs_b, c);
319  enq.set_query(q);
320  (void)enq.get_mset(0, 10));
321 
324  enq.set_query(q);
325  (void)enq.get_mset(0, 10));
326 
328  Xapian::Query q(Xapian::Query::OP_PHRASE, a_near_b, c);
329  enq.set_query(q);
330  (void)enq.get_mset(0, 10));
331 
333  Xapian::Query q(Xapian::Query::OP_PHRASE, a_phrs_b, c);
334  enq.set_query(q);
335  (void)enq.get_mset(0, 10));
336 }
337 
339 // time.
340 DEFINE_TESTCASE(xor3, backend) {
341  Xapian::Database db = get_database("apitest_simpledata");
342 
343  static const char * const subqs[] = {
344  "hack", "which", "paragraph", "is", "return"
345  };
346  // Document where the subqueries run out *does* match XOR:
347  Xapian::Query q(Xapian::Query::OP_XOR, subqs, subqs + 5);
348  Xapian::Enquire enq(db);
349  enq.set_query(q);
350  Xapian::MSet mset = enq.get_mset(0, 10);
351 
352  TEST_EQUAL(mset.size(), 3);
353  TEST_EQUAL(*mset[0], 4);
354  TEST_EQUAL(*mset[1], 2);
355  TEST_EQUAL(*mset[2], 3);
356 
357  // Document where the subqueries run out *does not* match XOR:
358  q = Xapian::Query(Xapian::Query::OP_XOR, subqs, subqs + 4);
359  enq.set_query(q);
360  mset = enq.get_mset(0, 10);
361 
362  TEST_EQUAL(mset.size(), 4);
363  TEST_EQUAL(*mset[0], 5);
364  TEST_EQUAL(*mset[1], 4);
365  TEST_EQUAL(*mset[2], 2);
366  TEST_EQUAL(*mset[3], 3);
367 }
368 
370 DEFINE_TESTCASE(nonutf8termdesc1, !backend) {
371  TEST_EQUAL(Xapian::Query("\xc0\x80\xf5\x80\x80\x80\xfe\xff").get_description(),
372  "Query(\\xc0\\x80\\xf5\\x80\\x80\\x80\\xfe\\xff)");
373  TEST_EQUAL(Xapian::Query(string("\x00\x1f", 2)).get_description(),
374  "Query(\\x00\\x1f)");
375  // Check that backslashes are encoded so output isn't ambiguous.
376  TEST_EQUAL(Xapian::Query("back\\slash").get_description(),
377  "Query(back\\x5cslash)");
378  // Check that \x7f is escaped.
379  TEST_EQUAL(Xapian::Query("D\x7f_\x7f~").get_description(),
380  "Query(D\\x7f_\\x7f~)");
381 }
382 
384 DEFINE_TESTCASE(queryintro1, !backend) {
386  TEST_EQUAL(Xapian::Query::MatchAll.get_num_subqueries(), 0);
388  TEST_EQUAL(Xapian::Query::MatchNothing.get_num_subqueries(), 0);
389 
390  Xapian::Query q;
396 
397  q = Xapian::Query("foo") & Xapian::Query("bar");
398  TEST_EQUAL(q.get_type(), q.OP_AND);
399 
400  q = Xapian::Query("foo") &~ Xapian::Query("bar");
401  TEST_EQUAL(q.get_type(), q.OP_AND_NOT);
402 
403  q = ~Xapian::Query("bar");
404  TEST_EQUAL(q.get_type(), q.OP_AND_NOT);
405 
406  q = Xapian::Query("foo") | Xapian::Query("bar");
407  TEST_EQUAL(q.get_type(), q.OP_OR);
408 
409  q = Xapian::Query("foo") ^ Xapian::Query("bar");
410  TEST_EQUAL(q.get_type(), q.OP_XOR);
411 
412  q = 1.25 * (Xapian::Query("one") | Xapian::Query("two"));
413  TEST_EQUAL(q.get_type(), q.OP_SCALE_WEIGHT);
414  TEST_EQUAL(q.get_num_subqueries(), 1);
415  TEST_EQUAL(q.get_subquery(0).get_type(), q.OP_OR);
416 
417  q = Xapian::Query("one") / 2.0;
418  TEST_EQUAL(q.get_type(), q.OP_SCALE_WEIGHT);
419  TEST_EQUAL(q.get_num_subqueries(), 1);
420  TEST_EQUAL(q.get_subquery(0).get_type(), q.LEAF_TERM);
421 
422  q = Xapian::Query(q.OP_NEAR, Xapian::Query("a"), Xapian::Query("b"));
423  TEST_EQUAL(q.get_type(), q.OP_NEAR);
424  TEST_EQUAL(q.get_num_subqueries(), 2);
425  TEST_EQUAL(q.get_subquery(0).get_type(), q.LEAF_TERM);
426  TEST_EQUAL(q.get_subquery(1).get_type(), q.LEAF_TERM);
427 
428  q = Xapian::Query(q.OP_PHRASE, Xapian::Query("c"), Xapian::Query("d"));
429  TEST_EQUAL(q.get_type(), q.OP_PHRASE);
430  TEST_EQUAL(q.get_num_subqueries(), 2);
431  TEST_EQUAL(q.get_subquery(0).get_type(), q.LEAF_TERM);
432  TEST_EQUAL(q.get_subquery(1).get_type(), q.LEAF_TERM);
433 }
434 
436 // We were incorrectly converting a term which indexed all docs and was used
437 // in an unweighted phrase into an all docs postlist, so check that this
438 // case actually works.
439 DEFINE_TESTCASE(phrasealldocs1, backend) {
440  Xapian::Database db = get_database("apitest_declen");
441  Xapian::Query q;
442  static const char * const phrase[] = { "this", "is", "the" };
444  Xapian::Query("paragraph"),
445  Xapian::Query(q.OP_PHRASE, phrase, phrase + 3));
446  Xapian::Enquire enq(db);
447  enq.set_query(q);
448  Xapian::MSet mset = enq.get_mset(0, 10);
449  TEST_EQUAL(mset.size(), 3);
450 }
451 
453  const char * pattern;
455  char max_type;
456  const char * terms[4];
457 };
458 
459 #define WILDCARD_EXCEPTION { 0, 0, 0, "" }
460 static const
462  // Tries to expand to 7 terms.
463  { "th", 6, 'E', WILDCARD_EXCEPTION },
464  { "thou", 1, 'E', { "though", 0, 0, 0 } },
465  { "s", 2, 'F', { "say", "search", 0, 0 } },
466  { "s", 2, 'M', { "simpl", "so", 0, 0 } }
467 };
468 
469 DEFINE_TESTCASE(wildcard1, backend) {
470  // FIXME: The counting of terms the wildcard expands to is per subdatabase,
471  // so the wildcard may expand to more terms than the limit if some aren't
472  // in all subdatabases. Also WILDCARD_LIMIT_MOST_FREQUENT uses the
473  // frequency from the subdatabase, and so may select different terms in
474  // each subdatabase.
475  SKIP_TEST_FOR_BACKEND("multi");
476  Xapian::Database db = get_database("apitest_simpledata");
477  Xapian::Enquire enq(db);
479 
480  for (auto&& test : wildcard1_testcases) {
481  tout << test.pattern << '\n';
482  auto tend = test.terms + 4;
483  while (tend[-1] == NULL) --tend;
484  bool expect_exception = (tend - test.terms == 4 && tend[-1][0] == '\0');
485  Xapian::Query q;
486  if (test.max_type) {
487  int max_type;
488  switch (test.max_type) {
489  case 'E':
491  break;
492  case 'F':
494  break;
495  case 'M':
497  break;
498  default:
499  FAIL_TEST("Unexpected max_type value");
500  }
501  q = Xapian::Query(o, test.pattern, test.max_expansion, max_type);
502  } else {
503  q = Xapian::Query(o, test.pattern, test.max_expansion);
504  }
505  enq.set_query(q);
506  try {
507  Xapian::MSet mset = enq.get_mset(0, 10);
508  TEST(!expect_exception);
509  q = Xapian::Query(q.OP_SYNONYM, test.terms, tend);
510  enq.set_query(q);
511  Xapian::MSet mset2 = enq.get_mset(0, 10);
512  TEST_EQUAL(mset.size(), mset2.size());
513  TEST(mset_range_is_same(mset, 0, mset2, 0, mset.size()));
514  } catch (const Xapian::WildcardError &) {
515  TEST(expect_exception);
516  }
517  }
518 }
519 
521 DEFINE_TESTCASE(wildcard2, backend) {
522  // FIXME: The counting of terms the wildcard expands to is per subdatabase,
523  // so the wildcard may expand to more terms than the limit if some aren't
524  // in all subdatabases. Also WILDCARD_LIMIT_MOST_FREQUENT uses the
525  // frequency from the subdatabase, and so may select different terms in
526  // each subdatabase.
527  SKIP_TEST_FOR_BACKEND("multi");
528  Xapian::Database db = get_database("apitest_simpledata");
529  Xapian::Enquire enq(db);
531 
532  const int max_type = Xapian::Query::WILDCARD_LIMIT_MOST_FREQUENT;
533  Xapian::Query q0(o, "w", 2, max_type);
534  Xapian::Query q(o, "s", 2, max_type);
535  Xapian::Query q2(o, "t", 2, max_type);
536  q = Xapian::Query(q.OP_OR, q0, q);
537  q = Xapian::Query(q.OP_OR, q, q2);
538  enq.set_query(q);
539  Xapian::MSet mset = enq.get_mset(0, 10);
540  TEST_EQUAL(mset.size(), 6);
541 }
542 
547 DEFINE_TESTCASE(wildcard4, backend) {
548  Xapian::Database db = get_database("apitest_simpledata");
549  Xapian::Enquire enq(db);
553  q |= Xapian::Query("xyzzy");
554  q |= Xapian::Query("use");
555  enq.set_query(q);
556  Xapian::MSet mset = enq.get_mset(0, 10);
557  TEST_EQUAL(mset.size(), 4);
558  TEST_EQUAL(mset[0].get_percent(), 25);
559  TEST_EQUAL_DOUBLE(mset.get_termweight("up"), 1.48489483900601);
560  // The exact termweight value here depends on the backend, but before the
561  // bug fix we were doubling the termweight of "use".
562  TEST_REL(mset.get_termweight("use"), <, 0.9);
563  TEST_EQUAL(mset.get_termweight("xyzzy"), 0.0);
564  // Enquire::get_matching_terms_begin() doesn't report terms from wildcard
565  // expansion, but it should report an explicit query term which also
566  // happens be in a wildcard expansion.
567  string terms;
568  for (auto t = enq.get_matching_terms_begin(*mset[1]);
569  t != enq.get_matching_terms_end(*mset[1]);
570  ++t) {
571  if (!terms.empty()) terms += ' ';
572  terms += *t;
573  }
574  TEST_EQUAL(terms, "use");
575 }
576 
577 DEFINE_TESTCASE(dualprefixwildcard1, backend) {
578  Xapian::Database db = get_database("apitest_simpledata");
582  tout << q.get_description() << '\n';
583  Xapian::Enquire enq(db);
584  enq.set_query(q);
585  TEST_EQUAL(enq.get_mset(0, 5).size(), 2);
586 }
587 
589  int window;
590  const char * terms[4];
592 };
593 
594 static const
596  { 5, { "expect", "to", "mset", 0 }, 0 },
597  { 5, { "word", "well", "the", 0 }, 2 },
598  { 5, { "if", "word", "doesnt", 0 }, 0 },
599  { 5, { "at", "line", "three", 0 }, 0 },
600  { 5, { "paragraph", "other", "the", 0 }, 0 },
601  { 5, { "other", "the", "with", 0 }, 0 }
602 };
603 
605 DEFINE_TESTCASE(loosephrase1, backend) {
606  Xapian::Database db = get_database("apitest_simpledata");
607  Xapian::Enquire enq(db);
608 
609  for (auto&& test : loosephrase1_testcases) {
610  auto tend = test.terms + 4;
611  while (tend[-1] == NULL) --tend;
612  auto OP_PHRASE = Xapian::Query::OP_PHRASE;
613  Xapian::Query q(OP_PHRASE, test.terms, tend, test.window);
614  enq.set_query(q);
615  Xapian::MSet mset = enq.get_mset(0, 10);
616  if (test.result == 0) {
617  TEST(mset.empty());
618  } else {
619  TEST_EQUAL(mset.size(), 1);
620  TEST_EQUAL(*mset[0], test.result);
621  }
622  }
623 }
624 
625 static const
627  { 4, { "test", "the", "with", 0 }, 1 },
628  { 4, { "expect", "word", "the", 0 }, 2 },
629  { 4, { "line", "be", "blank", 0 }, 1 },
630  { 2, { "banana", "banana", 0, 0 }, 0 },
631  { 3, { "banana", "banana", 0, 0 }, 0 },
632  { 2, { "word", "word", 0, 0 }, 2 },
633  { 4, { "work", "meant", "work", 0 }, 0 },
634  { 4, { "this", "one", "yet", "one" }, 0 }
635 };
636 
638 DEFINE_TESTCASE(loosenear1, backend) {
639  Xapian::Database db = get_database("apitest_simpledata");
640  Xapian::Enquire enq(db);
641 
642  for (auto&& test : loosenear1_testcases) {
643  auto tend = test.terms + 4;
644  while (tend[-1] == NULL) --tend;
645  Xapian::Query q(Xapian::Query::OP_NEAR, test.terms, tend, test.window);
646  enq.set_query(q);
647  Xapian::MSet mset = enq.get_mset(0, 10);
648  if (test.result == 0) {
649  TEST(mset.empty());
650  } else {
651  TEST_EQUAL(mset.size(), 1);
652  TEST_EQUAL(*mset[0], test.result);
653  }
654  }
655 }
656 
658 DEFINE_TESTCASE(complexphrase1, backend) {
659  Xapian::Database db = get_database("apitest_simpledata");
660  Xapian::Enquire enq(db);
662  Xapian::Query("a") | Xapian::Query("b"),
663  Xapian::Query("i"));
664  enq.set_query(query);
665  TEST(enq.get_mset(0, 10).empty());
667  Xapian::Query("a") | Xapian::Query("b"),
668  Xapian::Query("c"));
669  enq.set_query(query2);
670  TEST(enq.get_mset(0, 10).empty());
671 }
672 
674 DEFINE_TESTCASE(complexnear1, backend) {
675  Xapian::Database db = get_database("apitest_simpledata");
676  Xapian::Enquire enq(db);
678  Xapian::Query("a") | Xapian::Query("b"),
679  Xapian::Query("i"));
680  enq.set_query(query);
681  TEST(enq.get_mset(0, 10).empty());
683  Xapian::Query("a") | Xapian::Query("b"),
684  Xapian::Query("c"));
685  enq.set_query(query2);
686  TEST(enq.get_mset(0, 10).empty());
687 }
688 
690 DEFINE_TESTCASE(complexphrase2, backend) {
691  Xapian::Database db = get_database("apitest_simpledata");
692  Xapian::Enquire enq(db);
694  Xapian::Query subqs[3] = {
696  Xapian::Query("a"),
697  Xapian::Query(&ps)),
699  Xapian::Query("and"),
702  Xapian::Query("at"),
704  };
705  Xapian::Query query(Xapian::Query::OP_OR, subqs, subqs + 3);
706  enq.set_query(query);
707  (void)enq.get_mset(0, 10);
708 }
709 
711 DEFINE_TESTCASE(complexnear2, backend) {
712  Xapian::Database db = get_database("apitest_simpledata");
713  Xapian::Enquire enq(db);
715  Xapian::Query subqs[3] = {
717  Xapian::Query("a"),
718  Xapian::Query(&ps)),
720  Xapian::Query("and"),
723  Xapian::Query("at"),
725  };
726  Xapian::Query query(Xapian::Query::OP_OR, subqs, subqs + 3);
727  enq.set_query(query);
728  (void)enq.get_mset(0, 10);
729 }
730 
732 DEFINE_TESTCASE(zeroestimate1, backend) {
733  Xapian::Enquire enquire(get_database("apitest_simpledata"));
735  Xapian::Query("absolute"),
736  Xapian::Query("rubbish"));
737  enquire.set_query(phrase &~ Xapian::Query("queri"));
738  Xapian::MSet mset = enquire.get_mset(0, 0);
740 }
741 
743 DEFINE_TESTCASE(complexphrase3, backend) {
744  Xapian::Database db = get_database("apitest_simpledata");
745  Xapian::Enquire enq(db);
747  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"),
748  Xapian::Query("a"));
749  enq.set_query(query);
750  mset_expect_order(enq.get_mset(0, 10), 1);
752  Xapian::Query("a"),
753  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"));
754  enq.set_query(query2);
755  mset_expect_order(enq.get_mset(0, 10));
757  Xapian::Query("one") | Xapian::Query("with"),
758  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"));
759  enq.set_query(query3);
760  mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
762  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"),
763  Xapian::Query("one") | Xapian::Query("with"));
764  enq.set_query(query4);
765  mset_expect_order(enq.get_mset(0, 10));
766 }
767 
769 DEFINE_TESTCASE(complexnear3, backend) {
770  Xapian::Database db = get_database("apitest_simpledata");
771  Xapian::Enquire enq(db);
773  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"),
774  Xapian::Query("a"));
775  enq.set_query(query);
776  mset_expect_order(enq.get_mset(0, 10), 1);
778  Xapian::Query("a"),
779  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"));
780  enq.set_query(query2);
781  mset_expect_order(enq.get_mset(0, 10), 1);
783  Xapian::Query("one") | Xapian::Query("with"),
784  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"));
785  enq.set_query(query3);
786  mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
788  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"),
789  Xapian::Query("one") | Xapian::Query("with"));
790  enq.set_query(query4);
791  mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
792 }
793 
794 static void
796 {
797  Xapian::Document doc;
798  doc.add_term("this");
799  doc.add_term("paragraph");
800  doc.add_term("wibble", 5);
801  db.add_document(doc);
802 }
803 
804 DEFINE_TESTCASE(subdbwithoutpos1, backend) {
805  XFAIL_FOR_BACKEND("remote",
806  "Known but obscure remote bug which doesn't justify "
807  "protocol version bump");
808  XFAIL_FOR_BACKEND("multi_remote",
809  "Known but obscure remote bug which doesn't justify "
810  "protocol version bump");
811 
812  Xapian::Database db(get_database("apitest_simpledata"));
813 
815  Xapian::Query("this"),
816  Xapian::Query("paragraph"));
817 
818  Xapian::Enquire enq1(db);
819  enq1.set_query(q);
820  Xapian::MSet mset1 = enq1.get_mset(0, 10);
821  TEST_EQUAL(mset1.size(), 3);
822 
823  Xapian::Database db2 =
824  get_database("subdbwithoutpos1", gen_subdbwithoutpos1_db);
825 
826  // If a database has no positional info, OP_PHRASE -> OP_AND.
827  Xapian::Enquire enq2(db2);
828  enq2.set_query(q);
829  Xapian::MSet mset2 = enq2.get_mset(0, 10);
830  TEST_EQUAL(mset2.size(), 1);
831 
832  // If one sub-database in a combined database has no positional info but
833  // other sub-databases do, then we shouldn't convert OP_PHRASE to OP_AND
834  // (but prior to 1.4.3 we did).
835  db.add_database(db2);
836  Xapian::Enquire enq3(db);
837  enq3.set_query(q);
838  Xapian::MSet mset3 = enq3.get_mset(0, 10);
839  TEST_EQUAL(mset3.size(), 3);
840  // Regression test for bug introduced in 1.4.3 which led to a division by
841  // zero and then (at least on Linux) we got 1% here.
842  TEST_EQUAL(mset3[0].get_percent(), 100);
843 
844  // Regression test for https://trac.xapian.org/ticket/752
845  enq3.set_query((Xapian::Query("this") & q) | Xapian::Query("wibble"));
846  mset3 = enq3.get_mset(0, 10);
847  TEST_EQUAL(mset3.size(), 4);
848 }
849 
850 // Regression test for bug fixed in 1.4.4 and 1.2.25.
851 DEFINE_TESTCASE(notandor1, backend) {
852  Xapian::Database db(get_database("etext"));
853  using Xapian::Query;
854  Query q = Query("the") &~ (Query("friedrich") &
855  (Query("day") | Query("night")));
856  Xapian::Enquire enq(db);
857  enq.set_query(q);
858 
859  Xapian::MSet mset = enq.get_mset(0, 10, db.get_doccount());
860  TEST_EQUAL(mset.get_matches_estimated(), 344);
861 }
862 
863 // Regression test for bug fixed in git master before 1.5.0.
864 DEFINE_TESTCASE(boolorbug1, backend) {
865  Xapian::Database db(get_database("etext"));
866  using Xapian::Query;
867  Query q = Query("the") &~ Query(Query::OP_WILDCARD, "pru");
868  Xapian::Enquire enq(db);
869  enq.set_query(q);
870 
871  Xapian::MSet mset = enq.get_mset(0, 10, db.get_doccount());
872  // Due to a bug in BoolOrPostList this returned 330 results.
873  TEST_EQUAL(mset.get_matches_estimated(), 331);
874 }
875 
876 // Regression test for bug introduced in 1.4.13 and fixed in 1.4.14.
877 DEFINE_TESTCASE(hoistnotbug1, backend) {
878  Xapian::Database db(get_database("etext"));
879  using Xapian::Query;
880  Query q(Query::OP_PHRASE, Query("the"), Query("king"));
881  q &= ~Query("worldtornado");
882  q &= Query("a");
883  Xapian::Enquire enq(db);
884  enq.set_query(q);
885 
886  // This reliably fails before the fix in an assertion build, and may crash
887  // in other builds.
888  Xapian::MSet mset = enq.get_mset(0, 10, db.get_doccount());
889  TEST_EQUAL(mset.get_matches_estimated(), 42);
890 }
891 
892 // Regression test for segfault optimising query on git master before 1.5.0.
893 DEFINE_TESTCASE(emptynot1, backend) {
894  Xapian::Database db(get_database("apitest_simpledata"));
895  Xapian::Enquire enq(db);
897  Xapian::Query query = Xapian::Query("document") & Xapian::Query("api");
898  // This range won't match anything, so collapses to MatchNothing as we
899  // optimise the query.
900  query = Xapian::Query(query.OP_AND_NOT,
901  query,
903  enq.set_query(query);
904  Xapian::MSet mset = enq.get_mset(0, 10);
905  TEST_EQUAL(mset.size(), 1);
906  // Essentially the same test but with a term which doesn't match anything
907  // instead of a range.
908  query = Xapian::Query("document") & Xapian::Query("api");
909  query = Xapian::Query(query.OP_AND_NOT,
910  query,
911  Xapian::Query("nosuchterm"));
912  enq.set_query(query);
913  mset = enq.get_mset(0, 10);
914  TEST_EQUAL(mset.size(), 1);
915 }
916 
917 // Similar case to emptynot1 but for OP_AND_MAYBE. This case wasn't failing,
918 // so this isn't a regression test, but we do want to ensure it works.
919 DEFINE_TESTCASE(emptymaybe1, backend) {
920  Xapian::Database db(get_database("apitest_simpledata"));
921  Xapian::Enquire enq(db);
923  Xapian::Query query = Xapian::Query("document") & Xapian::Query("api");
924  // This range won't match anything, so collapses to MatchNothing as we
925  // optimise the query.
926  query = Xapian::Query(query.OP_AND_MAYBE,
927  query,
929  enq.set_query(query);
930  Xapian::MSet mset = enq.get_mset(0, 10);
931  TEST_EQUAL(mset.size(), 1);
932  // Essentially the same test but with a term which doesn't match anything
933  // instead of a range.
934  query = Xapian::Query("document") & Xapian::Query("api");
935  query = Xapian::Query(query.OP_AND_MAYBE,
936  query,
937  Xapian::Query("nosuchterm"));
938  enq.set_query(query);
939  mset = enq.get_mset(0, 10);
940  TEST_EQUAL(mset.size(), 1);
941 }
942 
943 DEFINE_TESTCASE(phraseweightcheckbug1, backend) {
944  Xapian::Database db(get_database("phraseweightcheckbug1"));
945  Xapian::Enquire enq(db);
946  static const char* const words[] = {"hello", "world"};
947  Xapian::Query query{Xapian::Query::OP_PHRASE, begin(words), end(words), 2};
949  tout << query.get_description() << '\n';
950  enq.set_query(query);
951  Xapian::MSet mset = enq.get_mset(0, 3);
952  TEST_EQUAL(mset.size(), 3);
953 }
954 
955 DEFINE_TESTCASE(orphanedhint1, backend) {
956  Xapian::Database db(get_database("apitest_simpledata"));
957  Xapian::Enquire enq(db);
958  auto OP_WILDCARD = Xapian::Query::OP_WILDCARD;
959  Xapian::Query query = Xapian::Query(OP_WILDCARD, "doc") &
960  Xapian::Query(OP_WILDCARD, "xyzzy");
961  query |= Xapian::Query("test");
962  tout << query.get_description() << '\n';
963  enq.set_query(query);
964  Xapian::MSet mset = enq.get_mset(0, 3);
965  TEST_EQUAL(mset.size(), 1);
966 }
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:454
#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
TermIterator get_matching_terms_end(Xapian::docid) const
End iterator corresponding to get_matching_terms_begin()
Definition: enquire.h:717
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:300
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:938
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:459
const char * pattern
Definition: api_query.cc:453
TermIterator get_matching_terms_begin(Xapian::docid did) const
Get terms which match a given document, by document id.
Definition: omenquire.cc:962
test functionality of the Xapian API
unsigned XAPIAN_TERMCOUNT_BASE_TYPE termcount
A counts of terms.
Definition: types.h:72
#define TEST_REL(A, REL, B)
Test a relation holds,e.g. TEST_REL(a,>,b);.
Definition: testmacros.h:32
Class implementing a "boolean" weighting scheme.
Definition: weight.h:433
Limit OP_WILDCARD expansion to the most frequent terms.
Definition: query.h:307
static const wildcard_testcase wildcard1_testcases[]
Definition: api_query.cc:461
This class provides read/write access to a database.
Definition: database.h:789
Value returned by get_type() for MatchAll or equivalent.
Definition: query.h:276
std::ostringstream tout
The debug printing stream.
Definition: testsuite.cc:104
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:626
static void gen_subdbwithoutpos1_db(Xapian::WritableDatabase &db, const string &)
Definition: api_query.cc:795
Throw an error if OP_WILDCARD exceeds its expansion limit.
Definition: query.h:291
static const positional_testcase loosephrase1_testcases[]
Definition: api_query.cc:595
Xapian::docid result
Definition: api_query.cc:591
double get_termweight(const std::string &term) const
Get the term weight of a term.
Definition: omenquire.cc:222
#define TEST_EQUAL_DOUBLE(a, b)
Test two doubles for near equality.
Definition: testsuite.h:295
#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