xapian-core  2.0.0
api_sorting.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2007,2008,2009,2012,2017,2019 Olly Betts
5  * Copyright (C) 2010 Richard Boulton
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 
24 #include "api_sorting.h"
25 
26 #include <xapian.h>
27 
28 #include "apitest.h"
29 #include "testutils.h"
30 
31 using namespace std;
32 
33 DEFINE_TESTCASE(sortfunctor1, backend) {
34  Xapian::Enquire enquire(get_database("apitest_sortrel"));
35  enquire.set_query(Xapian::Query("woman"));
36 
37  {
38  static const int keys[] = { 3, 1 };
40 
41  enquire.set_sort_by_key(&sorter, true);
42  Xapian::MSet mset = enquire.get_mset(0, 10);
43  mset_expect_order(mset, 2, 6, 7, 1, 3, 4, 5, 8, 9);
44 
45  for (auto m = mset.begin(); m != mset.end(); ++m) {
46  const string& data = m.get_document().get_data();
47  string exp;
48  exp += data[3];
49  exp += string(2, '\0');
50  exp += data[1];
51  TEST_EQUAL(m.get_sort_key(), exp);
52  }
53  }
54 
55  {
57  sorter.add_value(3);
58  sorter.add_value(1, true);
59 
60  enquire.set_sort_by_key(&sorter, true);
61  Xapian::MSet mset = enquire.get_mset(0, 10);
62  mset_expect_order(mset, 7, 6, 2, 8, 9, 4, 5, 1, 3);
63 
64  for (auto m = mset.begin(); m != mset.end(); ++m) {
65  const string& data = m.get_document().get_data();
66  string exp;
67  exp += data[3];
68  exp += string(2, '\0');
69  exp += char(0xff - data[1]);
70  exp += string(2, '\xff');
71  TEST_EQUAL(m.get_sort_key(), exp);
72  }
73  }
74 
75  {
77  sorter.add_value(100); // Value 100 isn't set.
78  sorter.add_value(3);
79  sorter.add_value(1, true);
80 
81  enquire.set_sort_by_key(&sorter, true);
82  Xapian::MSet mset = enquire.get_mset(0, 10);
83  mset_expect_order(mset, 7, 6, 2, 8, 9, 4, 5, 1, 3);
84 
85  for (auto m = mset.begin(); m != mset.end(); ++m) {
86  const string& data = m.get_document().get_data();
87  string exp;
88  exp += string(2, '\0');
89  exp += data[3];
90  exp += string(2, '\0');
91  exp += char(0xff - data[1]);
92  exp += string(2, '\xff');
93  TEST_EQUAL(m.get_sort_key(), exp);
94  }
95  }
96 
97  {
99  sorter.add_value(10); // Value 10 isn't always set.
100  sorter.add_value(1, true);
101 
102  enquire.set_sort_by_key(&sorter, true);
103  Xapian::MSet mset = enquire.get_mset(0, 10);
104  mset_expect_order(mset, 8, 9, 4, 5, 1, 3, 7, 6, 2);
105 
106  for (auto m = mset.begin(); m != mset.end(); ++m) {
107  const string& data = m.get_document().get_data();
108  string exp;
109  if (data.size() > 10) exp += data[10];
110  exp += string(2, '\0');
111  exp += char(0xff - data[1]);
112  exp += string(2, '\xff');
113  TEST_EQUAL(m.get_sort_key(), exp);
114  }
115  }
116 }
117 
119 DEFINE_TESTCASE(sortfunctor2, backend) {
120  Xapian::Database db = get_database("sortfunctor2",
121  [](Xapian::WritableDatabase& wdb,
122  const string&) {
123  Xapian::Document doc;
124  doc.add_term("foo");
125  doc.add_value(0, "ABB");
126  wdb.add_document(doc);
127  doc.add_value(0, "ABC");
128  wdb.add_document(doc);
129  doc.add_value(0, string("ABC", 4));
130  wdb.add_document(doc);
131  doc.add_value(0, "ABCD");
132  wdb.add_document(doc);
133  doc.add_value(0, "ABC\xff");
134  wdb.add_document(doc);
135  });
136 
137  Xapian::Enquire enquire(db);
138  enquire.set_query(Xapian::Query("foo"));
139 
140  {
142  sorter.add_value(0);
143  enquire.set_sort_by_key(&sorter, true);
144  Xapian::MSet mset = enquire.get_mset(0, 10);
145  mset_expect_order(mset, 5, 4, 3, 2, 1);
146  }
147 
148  {
150  sorter.add_value(0, true);
151  enquire.set_sort_by_key(&sorter, true);
152  Xapian::MSet mset = enquire.get_mset(0, 10);
153  mset_expect_order(mset, 1, 2, 3, 4, 5);
154  }
155 
156  {
158  sorter.add_value(0);
159  sorter.add_value(1);
160  enquire.set_sort_by_key(&sorter, true);
161  Xapian::MSet mset = enquire.get_mset(0, 10);
162  mset_expect_order(mset, 5, 4, 3, 2, 1);
163  }
164 
165  {
167  sorter.add_value(0, true);
168  sorter.add_value(1);
169  enquire.set_sort_by_key(&sorter, true);
170  Xapian::MSet mset = enquire.get_mset(0, 10);
171  mset_expect_order(mset, 1, 2, 3, 4, 5);
172  }
173 
174  {
176  sorter.add_value(0);
177  sorter.add_value(1, true);
178  enquire.set_sort_by_key(&sorter, true);
179  Xapian::MSet mset = enquire.get_mset(0, 10);
180  mset_expect_order(mset, 5, 4, 3, 2, 1);
181  }
182 
183  {
185  sorter.add_value(0, true);
186  sorter.add_value(1, true);
187  enquire.set_sort_by_key(&sorter, true);
188  Xapian::MSet mset = enquire.get_mset(0, 10);
189  mset_expect_order(mset, 1, 2, 3, 4, 5);
190  }
191 }
192 
193 // Test sort functor with some empty values.
194 DEFINE_TESTCASE(sortfunctor3, valuestats) {
195  Xapian::Database db(get_database("apitest_sortrel"));
196  Xapian::Enquire enquire(db);
197  enquire.set_query(Xapian::Query("woman"));
198 
199  // Value 10 is set to 'a' for 1, 3, 4, 5, 8, 9, and not set otherwise.
200  {
201  // Test default sort order - missing values come first.
203  sorter.add_value(10);
204 
205  enquire.set_sort_by_key(&sorter, false);
206  Xapian::MSet mset = enquire.get_mset(0, 10);
207  mset_expect_order(mset, 2, 6, 7, 1, 3, 4, 5, 8, 9);
208  }
209 
210  {
211  // Use a default value to put the missing values to the end.
213  sorter.add_value(10, false, db.get_value_upper_bound(10) + '\xff');
214 
215  enquire.set_sort_by_key(&sorter, false);
216  Xapian::MSet mset = enquire.get_mset(0, 10);
217  mset_expect_order(mset, 1, 3, 4, 5, 8, 9, 2, 6, 7);
218  }
219 
220  {
221  // Test using a default value and sorting in reverse order
223  sorter.add_value(10, false, db.get_value_upper_bound(10) + '\xff');
224 
225  enquire.set_sort_by_key(&sorter, true);
226  Xapian::MSet mset = enquire.get_mset(0, 10);
227  mset_expect_order(mset, 2, 6, 7, 1, 3, 4, 5, 8, 9);
228  }
229 
230  {
231  // Test using a default value and generating reverse order keys
233  sorter.add_value(10, true, db.get_value_upper_bound(10) + '\xff');
234 
235  enquire.set_sort_by_key(&sorter, false);
236  Xapian::MSet mset = enquire.get_mset(0, 10);
237  mset_expect_order(mset, 2, 6, 7, 1, 3, 4, 5, 8, 9);
238  }
239 
240  {
241  // Test using a default value, generating reverse order keys, and
242  // sorting in reverse order
244  sorter.add_value(10, true, db.get_value_upper_bound(10) + '\xff');
245 
246  enquire.set_sort_by_key(&sorter, true);
247  Xapian::MSet mset = enquire.get_mset(0, 10);
248  mset_expect_order(mset, 1, 3, 4, 5, 8, 9, 2, 6, 7);
249  }
250 }
251 
253  public:
254  std::string operator() (const Xapian::Document&) const override {
255  FAIL_TEST("NeverUseMeKeyMaker was called");
256  }
257 };
258 
260 DEFINE_TESTCASE(changesorter1, backend && !remote) {
261  Xapian::Enquire enquire(get_database("apitest_simpledata"));
262  enquire.set_query(Xapian::Query("word"));
263  NeverUseMeKeyMaker sorter;
264 
265  enquire.set_sort_by_key(&sorter, true);
266  enquire.set_sort_by_value(0, true);
267  Xapian::MSet mset = enquire.get_mset(0, 25);
268  TEST_EQUAL(mset.size(), 2); // Check that search is still doing something.
269 
270  enquire.set_sort_by_key(&sorter, true);
271  enquire.set_sort_by_value_then_relevance(0, true);
272  mset = enquire.get_mset(0, 25);
273  TEST_EQUAL(mset.size(), 2); // Check that search is still doing something.
274 
275  enquire.set_sort_by_key(&sorter, true);
276  enquire.set_sort_by_relevance_then_value(0, true);
277  mset = enquire.get_mset(0, 25);
278  TEST_EQUAL(mset.size(), 2); // Check that search is still doing something.
279 
280  enquire.set_sort_by_key(&sorter, true);
281  enquire.set_sort_by_relevance();
282  mset = enquire.get_mset(0, 25);
283  TEST_EQUAL(mset.size(), 2); // Check that search is still doing something.
284 
285  // Check that NeverUseMeKeyMaker::operator() would actually cause a test
286  // failure if called.
287  try {
288  sorter(Xapian::Document());
289  FAIL_TEST("NeverUseMeKeyMaker::operator() didn't throw TestFail");
290  } catch (const TestFail &) {
291  }
292 }
293 
295 DEFINE_TESTCASE(sortfunctorempty1, backend) {
296  Xapian::Enquire enquire(get_database("apitest_sortrel"));
297  enquire.set_query(Xapian::Query("woman"));
298 
299  {
300  int i;
301  Xapian::MultiValueKeyMaker sorter(&i, &i);
302 
303  enquire.set_sort_by_key(&sorter, true);
304  Xapian::MSet mset = enquire.get_mset(0, 10);
305  mset_expect_order(mset, 1, 2, 3, 4, 5, 6, 7, 8, 9);
306  }
307 }
308 
309 DEFINE_TESTCASE(multivaluekeymaker1, !backend) {
310  static const int keys[] = { 0, 1, 2, 3 };
311  Xapian::MultiValueKeyMaker sorter(keys, keys + 4);
312 
313  Xapian::Document doc;
314  TEST(sorter(doc).empty());
315 
316  doc.add_value(1, "foo");
317  TEST_EQUAL(sorter(doc), string("\0\0foo", 5));
318  doc.add_value(1, string("f\0o", 3));
319  TEST_EQUAL(sorter(doc), string("\0\0f\0\xffo", 6));
320  doc.add_value(3, "xyz");
321  TEST_EQUAL(sorter(doc), string("\0\0f\0\xffo\0\0\0\0xyz", 13));
322 
323  // An empty slot at the end, in reverse order, is terminated with \xff\xff
324  sorter.add_value(4, true);
325  TEST_EQUAL(sorter(doc), string("\0\0f\0\xffo\0\0\0\0xyz\0\0\xff\xff", 17));
326 
327  // An empty slot at the end, in ascending order, has no effect
328  sorter.add_value(0);
329  TEST_EQUAL(sorter(doc), string("\0\0f\0\xffo\0\0\0\0xyz\0\0\xff\xff", 17));
330 
331  // An empty slot at the end, with a default value
332  sorter.add_value(0, false, "hi");
333  TEST_EQUAL(sorter(doc), string("\0\0f\0\xffo\0\0\0\0xyz\0\0\xff\xff\0\0hi",
334  21));
335 
336  // An empty slot at the end, with a default value, in reverse sort order
337  sorter.add_value(0, true, "hi");
338  TEST_EQUAL(sorter(doc), string("\0\0f\0\xffo\0\0\0\0xyz\0\0\xff\xff\0\0hi"
339  "\0\0\x97\x96\xff\xff", 27));
340 }
341 
342 DEFINE_TESTCASE(sortfunctorremote1, remote) {
343  Xapian::Enquire enquire(get_database(string()));
344  NeverUseMeKeyMaker sorter;
345  enquire.set_query(Xapian::Query("word"));
346  enquire.set_sort_by_key(&sorter, true);
347  // NeverUseMeKeyMaker doesn't implemented serialise(), etc so should fail.
349  Xapian::MSet mset = enquire.get_mset(0, 10);
350  );
351 }
352 
353 DEFINE_TESTCASE(replace_weights1, backend) {
354  Xapian::Database mydb(get_database("apitest_onedoc"));
355  Xapian::Enquire enquire(mydb);
356  enquire.set_query(Xapian::Query("word"));
357  Xapian::MSet mymset = enquire.get_mset(0, 10);
358  // old_max_possible, max_attained = 0.269763689697702
359  double old_max_possible = mymset.get_max_possible();
360  const double new_weight = 0.125;
361  static const double weights[] = {new_weight};
362  mymset.replace_weights(begin(weights), end(weights));
363  Xapian::MSetIterator i = mymset.begin();
364  TEST(i != mymset.end());
365  TEST_EQUAL_DOUBLE(i.get_weight(), new_weight);
366  TEST_EQUAL_DOUBLE(mymset.get_max_attained(), new_weight);
367  TEST_EQUAL_DOUBLE(mymset.get_max_possible(), old_max_possible);
368 }
369 
370 DEFINE_TESTCASE(replace_weights2, backend) {
371  Xapian::Database mydb(get_database("apitest_onedoc"));
372  Xapian::Enquire enquire(mydb);
373  enquire.set_query(Xapian::Query("word"));
374  Xapian::MSet mymset = enquire.get_mset(0, 10);
375  static const double weights[] = {1.0, 2.0};
377  mymset.replace_weights(begin(weights), end(weights)));
378 }
379 
380 DEFINE_TESTCASE(sort_existing_mset_by_relevance, backend) {
381  Xapian::Database db = get_database("apitest_simpledata");
382  Xapian::Enquire enquire(db);
383  enquire.set_query(Xapian::Query("word"));
384  Xapian::MSet mymset = enquire.get_mset(0, 10);
385  static const Xapian::docid docids[] = {*mymset[0], *mymset[1]};
386  double max_weight = mymset.get_max_attained();
387  double weights[] = {max_weight * 0.5, max_weight + 1.0};
388  mymset.replace_weights(begin(weights), end(weights));
389  mymset.sort_by_relevance();
390  // The order of documents should have been reversed.
391  int k = 1;
392  for (Xapian::MSetIterator m = mymset.begin(); m != mymset.end();
393  ++m, --k) {
394  TEST_EQUAL(*m, docids[k]);
395  TEST_EQUAL_DOUBLE(m.get_weight(), weights[k]);
396  }
397  // Test that setting larger weights is reflected in these methods.
398  TEST_EQUAL_DOUBLE(mymset.get_max_attained(), weights[1]);
399  TEST_EQUAL_DOUBLE(mymset.get_max_possible(), weights[1]);
400 }
DEFINE_TESTCASE(sortfunctor1, backend)
Definition: api_sorting.cc:33
Xapian::Database get_database(const string &dbname)
Definition: apitest.cc:47
test functionality of the Xapian API
Class which is thrown when a test case fails.
Definition: testsuite.h:43
An indexed database of documents.
Definition: database.h:75
std::string get_value_upper_bound(Xapian::valueno slot) const
Get an upper bound on the values stored in the given value slot.
Definition: database.cc:296
Class representing a document.
Definition: document.h:64
void add_term(std::string_view term, Xapian::termcount wdf_inc=1)
Add a term to this document.
Definition: document.cc:87
void add_value(Xapian::valueno slot, std::string_view value)
Add a value to a slot in this document.
Definition: document.cc:191
Querying session.
Definition: enquire.h:57
void set_sort_by_key(KeyMaker *sorter, bool reverse) XAPIAN_NONNULL()
Set the sorting to be by key generated from values only.
Definition: enquire.cc:112
MSet get_mset(doccount first, doccount maxitems, doccount checkatleast=0, const RSet *rset=NULL, const MatchDecider *mdecider=NULL) const
Run the query.
Definition: enquire.cc:200
void set_sort_by_value_then_relevance(valueno sort_key, bool reverse)
Set the sorting to be by value, then by relevance for documents with the same value.
Definition: enquire.cc:123
void set_query(const Query &query, termcount query_length=0)
Set the query.
Definition: enquire.cc:72
void set_sort_by_relevance_then_value(valueno sort_key, bool reverse)
Set the sorting to be by relevance then value.
Definition: enquire.cc:144
void set_sort_by_relevance()
Set the sorting to be by relevance only.
Definition: enquire.cc:97
void set_sort_by_value(valueno sort_key, bool reverse)
Set the sorting to be by value only.
Definition: enquire.cc:103
InvalidArgumentError indicates an invalid parameter value was passed to the API.
Definition: error.h:229
Virtual base class for key making functors.
Definition: keymaker.h:44
Iterator over a Xapian::MSet.
Definition: mset.h:535
double get_weight() const
Get the weight for the current position.
Definition: msetiterator.cc:55
Class representing a list of search results.
Definition: mset.h:46
void sort_by_relevance()
Sorts the list of documents in MSet according to their weights.
Definition: mset.cc:268
Xapian::doccount size() const
Return number of items in this MSet object.
Definition: mset.cc:374
double get_max_possible() const
The maximum possible weight any document could achieve.
Definition: mset.cc:368
void replace_weights(Iterator first, Iterator last)
Assigns new weights and updates MSet.
Definition: mset.h:130
MSetIterator begin() const
Return iterator pointing to the first item in this MSet.
Definition: mset.h:786
double get_max_attained() const
The maximum weight attained by any document.
Definition: mset.cc:362
MSetIterator end() const
Return iterator pointing to just after the last item in this MSet.
Definition: mset.h:791
KeyMaker subclass which combines several values.
Definition: keymaker.h:156
void add_value(Xapian::valueno slot, bool reverse=false, std::string_view defvalue={})
Add a value slot to the list to build a key from.
Definition: keymaker.h:195
Class representing a query.
Definition: query.h:45
UnimplementedError indicates an attempt to use an unimplemented feature.
Definition: error.h:313
This class provides read/write access to a database.
Definition: database.h:964
Xapian::docid add_document(const Xapian::Document &doc)
Add a document to the database.
Definition: database.cc:561
unsigned XAPIAN_DOCID_BASE_TYPE docid
A unique identifier for a document.
Definition: types.h:51
#define FAIL_TEST(MSG)
Fail the current testcase with message MSG.
Definition: testsuite.h:65
#define TEST_EQUAL(a, b)
Test for equality of two things.
Definition: testsuite.h:276
#define TEST_EQUAL_DOUBLE(a, b)
Test two doubles for near equality.
Definition: testsuite.h:293
#define TEST(a)
Test a condition, without an additional explanation for failure.
Definition: testsuite.h:273
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:224
Xapian-specific test helper functions and macros.
#define TEST_EXCEPTION(TYPE, CODE)
Check that CODE throws exactly Xapian exception TYPE.
Definition: testutils.h:112
static bool keys
Public interfaces for the Xapian library.