xapian-core  1.4.25
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 
543 DEFINE_TESTCASE(dualprefixwildcard1, backend) {
544  Xapian::Database db = get_database("apitest_simpledata");
548  tout << q.get_description() << '\n';
549  Xapian::Enquire enq(db);
550  enq.set_query(q);
551  TEST_EQUAL(enq.get_mset(0, 5).size(), 2);
552 }
553 
555  int window;
556  const char * terms[4];
558 };
559 
560 static const
562  { 5, { "expect", "to", "mset", 0 }, 0 },
563  { 5, { "word", "well", "the", 0 }, 2 },
564  { 5, { "if", "word", "doesnt", 0 }, 0 },
565  { 5, { "at", "line", "three", 0 }, 0 },
566  { 5, { "paragraph", "other", "the", 0 }, 0 },
567  { 5, { "other", "the", "with", 0 }, 0 }
568 };
569 
571 DEFINE_TESTCASE(loosephrase1, backend) {
572  Xapian::Database db = get_database("apitest_simpledata");
573  Xapian::Enquire enq(db);
574 
575  for (auto&& test : loosephrase1_testcases) {
576  auto tend = test.terms + 4;
577  while (tend[-1] == NULL) --tend;
578  auto OP_PHRASE = Xapian::Query::OP_PHRASE;
579  Xapian::Query q(OP_PHRASE, test.terms, tend, test.window);
580  enq.set_query(q);
581  Xapian::MSet mset = enq.get_mset(0, 10);
582  if (test.result == 0) {
583  TEST(mset.empty());
584  } else {
585  TEST_EQUAL(mset.size(), 1);
586  TEST_EQUAL(*mset[0], test.result);
587  }
588  }
589 }
590 
591 static const
593  { 4, { "test", "the", "with", 0 }, 1 },
594  { 4, { "expect", "word", "the", 0 }, 2 },
595  { 4, { "line", "be", "blank", 0 }, 1 },
596  { 2, { "banana", "banana", 0, 0 }, 0 },
597  { 3, { "banana", "banana", 0, 0 }, 0 },
598  { 2, { "word", "word", 0, 0 }, 2 },
599  { 4, { "work", "meant", "work", 0 }, 0 },
600  { 4, { "this", "one", "yet", "one" }, 0 }
601 };
602 
604 DEFINE_TESTCASE(loosenear1, backend) {
605  Xapian::Database db = get_database("apitest_simpledata");
606  Xapian::Enquire enq(db);
607 
608  for (auto&& test : loosenear1_testcases) {
609  auto tend = test.terms + 4;
610  while (tend[-1] == NULL) --tend;
611  Xapian::Query q(Xapian::Query::OP_NEAR, test.terms, tend, test.window);
612  enq.set_query(q);
613  Xapian::MSet mset = enq.get_mset(0, 10);
614  if (test.result == 0) {
615  TEST(mset.empty());
616  } else {
617  TEST_EQUAL(mset.size(), 1);
618  TEST_EQUAL(*mset[0], test.result);
619  }
620  }
621 }
622 
624 DEFINE_TESTCASE(complexphrase1, backend) {
625  Xapian::Database db = get_database("apitest_simpledata");
626  Xapian::Enquire enq(db);
628  Xapian::Query("a") | Xapian::Query("b"),
629  Xapian::Query("i"));
630  enq.set_query(query);
631  TEST(enq.get_mset(0, 10).empty());
633  Xapian::Query("a") | Xapian::Query("b"),
634  Xapian::Query("c"));
635  enq.set_query(query2);
636  TEST(enq.get_mset(0, 10).empty());
637 }
638 
640 DEFINE_TESTCASE(complexnear1, backend) {
641  Xapian::Database db = get_database("apitest_simpledata");
642  Xapian::Enquire enq(db);
644  Xapian::Query("a") | Xapian::Query("b"),
645  Xapian::Query("i"));
646  enq.set_query(query);
647  TEST(enq.get_mset(0, 10).empty());
649  Xapian::Query("a") | Xapian::Query("b"),
650  Xapian::Query("c"));
651  enq.set_query(query2);
652  TEST(enq.get_mset(0, 10).empty());
653 }
654 
656 DEFINE_TESTCASE(complexphrase2, backend) {
657  Xapian::Database db = get_database("apitest_simpledata");
658  Xapian::Enquire enq(db);
660  Xapian::Query subqs[3] = {
662  Xapian::Query("a"),
663  Xapian::Query(&ps)),
665  Xapian::Query("and"),
668  Xapian::Query("at"),
670  };
671  Xapian::Query query(Xapian::Query::OP_OR, subqs, subqs + 3);
672  enq.set_query(query);
673  (void)enq.get_mset(0, 10);
674 }
675 
677 DEFINE_TESTCASE(complexnear2, backend) {
678  Xapian::Database db = get_database("apitest_simpledata");
679  Xapian::Enquire enq(db);
681  Xapian::Query subqs[3] = {
683  Xapian::Query("a"),
684  Xapian::Query(&ps)),
686  Xapian::Query("and"),
689  Xapian::Query("at"),
691  };
692  Xapian::Query query(Xapian::Query::OP_OR, subqs, subqs + 3);
693  enq.set_query(query);
694  (void)enq.get_mset(0, 10);
695 }
696 
698 DEFINE_TESTCASE(zeroestimate1, backend) {
699  Xapian::Enquire enquire(get_database("apitest_simpledata"));
701  Xapian::Query("absolute"),
702  Xapian::Query("rubbish"));
703  enquire.set_query(phrase &~ Xapian::Query("queri"));
704  Xapian::MSet mset = enquire.get_mset(0, 0);
706 }
707 
709 DEFINE_TESTCASE(complexphrase3, backend) {
710  Xapian::Database db = get_database("apitest_simpledata");
711  Xapian::Enquire enq(db);
713  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"),
714  Xapian::Query("a"));
715  enq.set_query(query);
716  mset_expect_order(enq.get_mset(0, 10), 1);
718  Xapian::Query("a"),
719  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"));
720  enq.set_query(query2);
721  mset_expect_order(enq.get_mset(0, 10));
723  Xapian::Query("one") | Xapian::Query("with"),
724  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"));
725  enq.set_query(query3);
726  mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
728  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"),
729  Xapian::Query("one") | Xapian::Query("with"));
730  enq.set_query(query4);
731  mset_expect_order(enq.get_mset(0, 10));
732 }
733 
735 DEFINE_TESTCASE(complexnear3, backend) {
736  Xapian::Database db = get_database("apitest_simpledata");
737  Xapian::Enquire enq(db);
739  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"),
740  Xapian::Query("a"));
741  enq.set_query(query);
742  mset_expect_order(enq.get_mset(0, 10), 1);
744  Xapian::Query("a"),
745  Xapian::Query("is") | Xapian::Query("as") | Xapian::Query("be"));
746  enq.set_query(query2);
747  mset_expect_order(enq.get_mset(0, 10), 1);
749  Xapian::Query("one") | Xapian::Query("with"),
750  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"));
751  enq.set_query(query3);
752  mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
754  Xapian::Query("the") | Xapian::Query("of") | Xapian::Query("line"),
755  Xapian::Query("one") | Xapian::Query("with"));
756  enq.set_query(query4);
757  mset_expect_order(enq.get_mset(0, 10), 1, 4, 5);
758 }
759 
760 static void
762 {
763  Xapian::Document doc;
764  doc.add_term("this");
765  doc.add_term("paragraph");
766  doc.add_term("wibble", 5);
767  db.add_document(doc);
768 }
769 
770 DEFINE_TESTCASE(subdbwithoutpos1, backend) {
771  XFAIL_FOR_BACKEND("remote",
772  "Known but obscure remote bug which doesn't justify "
773  "protocol version bump");
774  XFAIL_FOR_BACKEND("multi_remote",
775  "Known but obscure remote bug which doesn't justify "
776  "protocol version bump");
777 
778  Xapian::Database db(get_database("apitest_simpledata"));
779 
781  Xapian::Query("this"),
782  Xapian::Query("paragraph"));
783 
784  Xapian::Enquire enq1(db);
785  enq1.set_query(q);
786  Xapian::MSet mset1 = enq1.get_mset(0, 10);
787  TEST_EQUAL(mset1.size(), 3);
788 
789  Xapian::Database db2 =
790  get_database("subdbwithoutpos1", gen_subdbwithoutpos1_db);
791 
792  // If a database has no positional info, OP_PHRASE -> OP_AND.
793  Xapian::Enquire enq2(db2);
794  enq2.set_query(q);
795  Xapian::MSet mset2 = enq2.get_mset(0, 10);
796  TEST_EQUAL(mset2.size(), 1);
797 
798  // If one sub-database in a combined database has no positional info but
799  // other sub-databases do, then we shouldn't convert OP_PHRASE to OP_AND
800  // (but prior to 1.4.3 we did).
801  db.add_database(db2);
802  Xapian::Enquire enq3(db);
803  enq3.set_query(q);
804  Xapian::MSet mset3 = enq3.get_mset(0, 10);
805  TEST_EQUAL(mset3.size(), 3);
806  // Regression test for bug introduced in 1.4.3 which led to a division by
807  // zero and then (at least on Linux) we got 1% here.
808  TEST_EQUAL(mset3[0].get_percent(), 100);
809 
810  // Regression test for https://trac.xapian.org/ticket/752
811  enq3.set_query((Xapian::Query("this") & q) | Xapian::Query("wibble"));
812  mset3 = enq3.get_mset(0, 10);
813  TEST_EQUAL(mset3.size(), 4);
814 }
815 
816 // Regression test for bug fixed in 1.4.4 and 1.2.25.
817 DEFINE_TESTCASE(notandor1, backend) {
818  Xapian::Database db(get_database("etext"));
819  using Xapian::Query;
820  Query q = Query("the") &~ (Query("friedrich") &
821  (Query("day") | Query("night")));
822  Xapian::Enquire enq(db);
823  enq.set_query(q);
824 
825  Xapian::MSet mset = enq.get_mset(0, 10, db.get_doccount());
826  TEST_EQUAL(mset.get_matches_estimated(), 344);
827 }
828 
829 // Regression test for bug fixed in git master before 1.5.0.
830 DEFINE_TESTCASE(boolorbug1, backend) {
831  Xapian::Database db(get_database("etext"));
832  using Xapian::Query;
833  Query q = Query("the") &~ Query(Query::OP_WILDCARD, "pru");
834  Xapian::Enquire enq(db);
835  enq.set_query(q);
836 
837  Xapian::MSet mset = enq.get_mset(0, 10, db.get_doccount());
838  // Due to a bug in BoolOrPostList this returned 330 results.
839  TEST_EQUAL(mset.get_matches_estimated(), 331);
840 }
841 
842 // Regression test for bug introduced in 1.4.13 and fixed in 1.4.14.
843 DEFINE_TESTCASE(hoistnotbug1, backend) {
844  Xapian::Database db(get_database("etext"));
845  using Xapian::Query;
846  Query q(Query::OP_PHRASE, Query("the"), Query("king"));
847  q &= ~Query("worldtornado");
848  q &= Query("a");
849  Xapian::Enquire enq(db);
850  enq.set_query(q);
851 
852  // This reliably fails before the fix in an assertion build, and may crash
853  // in other builds.
854  Xapian::MSet mset = enq.get_mset(0, 10, db.get_doccount());
855  TEST_EQUAL(mset.get_matches_estimated(), 42);
856 }
857 
858 // Regression test for segfault optimising query on git master before 1.5.0.
859 DEFINE_TESTCASE(emptynot1, backend) {
860  Xapian::Database db(get_database("apitest_simpledata"));
861  Xapian::Enquire enq(db);
863  Xapian::Query query = Xapian::Query("document") & Xapian::Query("api");
864  // This range won't match anything, so collapses to MatchNothing as we
865  // optimise the query.
866  query = Xapian::Query(query.OP_AND_NOT,
867  query,
869  enq.set_query(query);
870  Xapian::MSet mset = enq.get_mset(0, 10);
871  TEST_EQUAL(mset.size(), 1);
872  // Essentially the same test but with a term which doesn't match anything
873  // instead of a range.
874  query = Xapian::Query("document") & Xapian::Query("api");
875  query = Xapian::Query(query.OP_AND_NOT,
876  query,
877  Xapian::Query("nosuchterm"));
878  enq.set_query(query);
879  mset = enq.get_mset(0, 10);
880  TEST_EQUAL(mset.size(), 1);
881 }
882 
883 // Similar case to emptynot1 but for OP_AND_MAYBE. This case wasn't failing,
884 // so this isn't a regression test, but we do want to ensure it works.
885 DEFINE_TESTCASE(emptymaybe1, backend) {
886  Xapian::Database db(get_database("apitest_simpledata"));
887  Xapian::Enquire enq(db);
889  Xapian::Query query = Xapian::Query("document") & Xapian::Query("api");
890  // This range won't match anything, so collapses to MatchNothing as we
891  // optimise the query.
892  query = Xapian::Query(query.OP_AND_MAYBE,
893  query,
895  enq.set_query(query);
896  Xapian::MSet mset = enq.get_mset(0, 10);
897  TEST_EQUAL(mset.size(), 1);
898  // Essentially the same test but with a term which doesn't match anything
899  // instead of a range.
900  query = Xapian::Query("document") & Xapian::Query("api");
901  query = Xapian::Query(query.OP_AND_MAYBE,
902  query,
903  Xapian::Query("nosuchterm"));
904  enq.set_query(query);
905  mset = enq.get_mset(0, 10);
906  TEST_EQUAL(mset.size(), 1);
907 }
908 
909 DEFINE_TESTCASE(phraseweightcheckbug1, backend) {
910  Xapian::Database db(get_database("phraseweightcheckbug1"));
911  Xapian::Enquire enq(db);
912  static const char* const words[] = {"hello", "world"};
913  Xapian::Query query{Xapian::Query::OP_PHRASE, begin(words), end(words), 2};
915  tout << query.get_description() << '\n';
916  enq.set_query(query);
917  Xapian::MSet mset = enq.get_mset(0, 3);
918  TEST_EQUAL(mset.size(), 3);
919 }
920 
921 DEFINE_TESTCASE(orphanedhint1, backend) {
922  Xapian::Database db(get_database("apitest_simpledata"));
923  Xapian::Enquire enq(db);
924  auto OP_WILDCARD = Xapian::Query::OP_WILDCARD;
925  Xapian::Query query = Xapian::Query(OP_WILDCARD, "doc") &
926  Xapian::Query(OP_WILDCARD, "xyzzy");
927  query |= Xapian::Query("test");
928  tout << query.get_description() << '\n';
929  enq.set_query(query);
930  Xapian::MSet mset = enq.get_mset(0, 3);
931  TEST_EQUAL(mset.size(), 1);
932 }
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
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: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:459
const char * pattern
Definition: api_query.cc:453
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: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: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:592
static void gen_subdbwithoutpos1_db(Xapian::WritableDatabase &db, const string &)
Definition: api_query.cc:761
Throw an error if OP_WILDCARD exceeds its expansion limit.
Definition: query.h:291
static const positional_testcase loosephrase1_testcases[]
Definition: api_query.cc:561
Xapian::docid result
Definition: api_query.cc:557
#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