Branch data Line data Source code
1 : : /** @file mset.cc
2 : : * @brief Xapian::MSet class
3 : : */
4 : : /* Copyright (C) 2017 Olly Betts
5 : : *
6 : : * This program is free software; you can redistribute it and/or modify
7 : : * it under the terms of the GNU General Public License as published by
8 : : * the Free Software Foundation; either version 2 of the License, or
9 : : * (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 USA
19 : : */
20 : :
21 : : #include <config.h>
22 : :
23 : : #include "msetinternal.h"
24 : : #include "xapian/mset.h"
25 : :
26 : : #include "net/length.h"
27 : : #include "net/serialise.h"
28 : : #include "matcher/msetcmp.h"
29 : : #include "roundestimate.h"
30 : : #include "serialise-double.h"
31 : : #include "str.h"
32 : : #include "unicode/description_append.h"
33 : :
34 : : #include <algorithm>
35 : : #include <cfloat>
36 : : #include <string>
37 : :
38 : : using namespace std;
39 : :
40 : : namespace Xapian {
41 : :
42 : : MSet::MSet(const MSet&) = default;
43 : :
44 : : MSet&
45 : : MSet::operator=(const MSet&) = default;
46 : :
47 : : MSet::MSet(MSet&&) = default;
48 : :
49 : : MSet&
50 : : MSet::operator=(MSet&&) = default;
51 : :
52 [ + - ]: 171912 : MSet::MSet() : internal(new MSet::Internal) {}
53 : :
54 : 299092 : MSet::MSet(Internal* internal_) : internal(internal_) {}
55 : :
56 : 67251694 : MSet::~MSet() {}
57 : :
58 : : void
59 : 28 : MSet::fetch_(Xapian::doccount first, Xapian::doccount last) const
60 : : {
61 : 28 : internal->fetch(first, last);
62 : 28 : }
63 : :
64 : : void
65 : 21 : MSet::set_item_weight(Xapian::doccount i, double weight)
66 : : {
67 : 21 : internal->set_item_weight(i, weight);
68 : 21 : }
69 : :
70 : : void
71 : 7 : MSet::sort_by_relevance()
72 : : {
73 : 14 : std::sort(internal->items.begin(), internal->items.end(),
74 : 21 : get_msetcmp_function(Enquire::Internal::REL, true, false));
75 : 7 : }
76 : :
77 : : int
78 : 48525 : MSet::convert_to_percent(double weight) const
79 : : {
80 : 48525 : return internal->convert_to_percent(weight);
81 : : }
82 : :
83 : : Xapian::doccount
84 : 127 : MSet::get_termfreq(const std::string& term) const
85 : : {
86 : : // Check the cached data for query terms first.
87 : : Xapian::doccount termfreq;
88 [ + + ][ + - ]: 127 : if (usual(internal->stats && internal->stats->get_stats(term, termfreq))) {
[ + + ][ + + ]
89 : 112 : return termfreq;
90 : : }
91 : :
92 [ + + ]: 15 : if (rare(internal->enquire.get() == NULL)) {
93 : : // Consistent with get_termfreq() on an empty database which always
94 : : // returns 0.
95 : 1 : return 0;
96 : : }
97 : :
98 : : // Fall back to asking the database via enquire.
99 [ + - ]: 127 : return internal->enquire->get_termfreq(term);
100 : : }
101 : :
102 : : double
103 : 99 : MSet::get_termweight(const std::string& term) const
104 : : {
105 : : // A term not in the query has no termweight, so 0.0 makes sense as the
106 : : // answer in such cases.
107 : 99 : double weight = 0.0;
108 [ + + ]: 99 : if (usual(internal->stats)) {
109 [ + - ]: 98 : (void)internal->stats->get_termweight(term, weight);
110 : : }
111 : 99 : return weight;
112 : : }
113 : :
114 : : Xapian::doccount
115 : 54 : MSet::get_firstitem() const
116 : : {
117 : 54 : return internal->first;
118 : : }
119 : :
120 : : Xapian::doccount
121 : 1097 : MSet::get_matches_lower_bound() const
122 : : {
123 : 1097 : return internal->matches_lower_bound;
124 : : }
125 : :
126 : : Xapian::doccount
127 : 810 : MSet::get_matches_estimated() const
128 : : {
129 : : // Doing this here avoids calculating if the estimate is never looked at,
130 : : // though does mean we recalculate if this method is called more than once.
131 : 810 : return round_estimate(internal->matches_lower_bound,
132 : 810 : internal->matches_upper_bound,
133 : 1620 : internal->matches_estimated);
134 : : }
135 : :
136 : : Xapian::doccount
137 : 950 : MSet::get_matches_upper_bound() const
138 : : {
139 : 950 : return internal->matches_upper_bound;
140 : : }
141 : :
142 : : Xapian::doccount
143 : 648 : MSet::get_uncollapsed_matches_lower_bound() const
144 : : {
145 : 648 : return internal->uncollapsed_lower_bound;
146 : : }
147 : :
148 : : Xapian::doccount
149 : 481 : MSet::get_uncollapsed_matches_estimated() const
150 : : {
151 : : // Doing this here avoids calculating if the estimate is never looked at,
152 : : // though does mean we recalculate if this method is called more than once.
153 : 481 : return round_estimate(internal->uncollapsed_lower_bound,
154 : 481 : internal->uncollapsed_upper_bound,
155 : 962 : internal->uncollapsed_estimated);
156 : : }
157 : :
158 : : Xapian::doccount
159 : 479 : MSet::get_uncollapsed_matches_upper_bound() const
160 : : {
161 : 479 : return internal->uncollapsed_upper_bound;
162 : : }
163 : :
164 : : double
165 : 61 : MSet::get_max_attained() const
166 : : {
167 : 61 : return internal->max_attained;
168 : : }
169 : :
170 : : double
171 : 224 : MSet::get_max_possible() const
172 : : {
173 : 224 : return internal->max_possible;
174 : : }
175 : :
176 : : Xapian::doccount
177 : 41791985 : MSet::size() const
178 : : {
179 : : Assert(internal.get());
180 : 41791985 : return internal->items.size();
181 : : }
182 : :
183 : : std::string
184 : 495 : MSet::snippet(const std::string& text,
185 : : size_t length,
186 : : const Xapian::Stem& stemmer,
187 : : unsigned flags,
188 : : const std::string& hi_start,
189 : : const std::string& hi_end,
190 : : const std::string& omit) const
191 : : {
192 : : // The actual implementation is in queryparser/termgenerator_internal.cc.
193 : : return internal->snippet(text, length, stemmer, flags,
194 : 495 : hi_start, hi_end, omit);
195 : : }
196 : :
197 : : std::string
198 : 124 : MSet::get_description() const
199 : : {
200 : 124 : return internal->get_description();
201 : : }
202 : :
203 : : Document
204 : 200039 : MSet::Internal::get_document(Xapian::doccount index) const
205 : : {
206 [ - + ]: 200039 : if (index >= items.size()) {
207 [ # # ]: 0 : string msg = "Requested index ";
208 [ # # ][ # # ]: 0 : msg += str(index);
209 [ # # ]: 0 : msg += " in MSet of size ";
210 [ # # ][ # # ]: 0 : msg += str(items.size());
211 [ # # ][ # # ]: 0 : throw Xapian::RangeError(msg);
212 : : }
213 : : Assert(enquire.get());
214 : 200039 : return enquire->get_document(items[index].get_docid());
215 : : }
216 : :
217 : : void
218 : 28 : MSet::Internal::fetch(Xapian::doccount first_, Xapian::doccount last) const
219 : : {
220 [ + - ][ - + ]: 28 : if (items.empty() || enquire.get() == NULL) {
[ - + ]
221 : 0 : return;
222 : : }
223 [ + + ]: 28 : if (last > items.size() - 1) {
224 : 14 : last = items.size() - 1;
225 : : }
226 [ + + ]: 28 : if (first_ <= last) {
227 : 7 : Xapian::doccount n = last - first_;
228 [ + + ]: 49 : for (Xapian::doccount i = 0; i <= n; ++i) {
229 : 42 : enquire->request_document(items[i].get_docid());
230 : : }
231 : : }
232 : : }
233 : :
234 : : void
235 : 21 : MSet::Internal::set_item_weight(Xapian::doccount i, double weight)
236 : : {
237 : : // max_attained is updated assuming that set_item_weight is called on every
238 : : // MSet item from 0 up. While assigning new weights max_attained is updated
239 : : // as the maximum of the new weights set till Xapian::doccount i.
240 [ + + ]: 21 : if (i == 0)
241 : 14 : max_attained = weight;
242 : : else
243 : 7 : max_attained = max(max_attained, weight);
244 : : // Ideally the max_possible should be the maximum possible weight that
245 : : // can be assigned by the reranking algorithm, but since it is not always
246 : : // possible to calculate the max possible weight for a reranking algorithm
247 : : // we use this approach.
248 : 21 : max_possible = max(max_possible, max_attained);
249 : 21 : items[i].set_weight(weight);
250 : 21 : }
251 : :
252 : : int
253 : 48525 : MSet::Internal::convert_to_percent(double weight) const
254 : : {
255 : : int percent;
256 [ - + ]: 48525 : if (percent_scale_factor == 0.0) {
257 : : // For an unweighted search, give all matches 100%.
258 : 0 : percent = 100;
259 [ - + ]: 48525 : } else if (weight <= 0.0) {
260 : : // Some weighting schemes can return zero relevance while matching,
261 : : // so give such matches 0%.
262 : 0 : percent = 0;
263 : : } else {
264 : : // Adding on 100 * DBL_EPSILON was a hack to work around excess
265 : : // precision (e.g. on x86 when not using SSE), but this code seems like
266 : : // it's generally asking for problems with floating point rounding
267 : : // issues - maybe we ought to carry through the matching and total
268 : : // number of subqueries and calculate using those instead.
269 : : //
270 : : // There are corresponding hacks in matcher/matcher.cc.
271 : 48525 : percent = int(weight * percent_scale_factor + 100.0 * DBL_EPSILON);
272 [ - + ]: 48525 : if (percent <= 0) {
273 : : // Make any non-zero weight give a non-zero percentage.
274 : 0 : percent = 1;
275 [ - + ]: 48525 : } else if (percent > 100) {
276 : : // Make sure we don't ever exceed 100%.
277 : 0 : percent = 100;
278 : : }
279 : : // FIXME: Ideally we should also make sure any non-exact match gives
280 : : // < 100%.
281 : : }
282 : 48525 : return percent;
283 : : }
284 : :
285 : : void
286 : 42 : MSet::Internal::unshard_docids(Xapian::doccount shard,
287 : : Xapian::doccount n_shards)
288 : : {
289 [ + + ]: 130 : for (auto& result : items) {
290 : 88 : result.unshard_docid(shard, n_shards);
291 : : }
292 : 42 : }
293 : :
294 : : void
295 : 54 : MSet::Internal::merge_stats(const Internal* o)
296 : : {
297 [ + - ]: 54 : if (snippet_bg_relevance.empty()) {
298 : 54 : snippet_bg_relevance = o->snippet_bg_relevance;
299 : : } else {
300 : : Assert(snippet_bg_relevance == o->snippet_bg_relevance);
301 : : }
302 : 54 : matches_lower_bound += o->matches_lower_bound;
303 : 54 : matches_estimated += o->matches_estimated;
304 : 54 : matches_upper_bound += o->matches_upper_bound;
305 : 54 : uncollapsed_lower_bound += o->uncollapsed_lower_bound;
306 : 54 : uncollapsed_estimated += o->uncollapsed_estimated;
307 : 54 : uncollapsed_upper_bound += o->uncollapsed_upper_bound;
308 : 54 : max_possible = max(max_possible, o->max_possible);
309 [ + + ]: 54 : if (o->max_attained > max_attained) {
310 : 24 : max_attained = o->max_attained;
311 : 24 : percent_scale_factor = o->percent_scale_factor;
312 : : }
313 : 54 : }
314 : :
315 : : string
316 : 10350 : MSet::Internal::serialise() const
317 : : {
318 : 10350 : string result;
319 : :
320 [ + - ][ + - ]: 10350 : result += encode_length(first);
321 : : // Send back the raw matches_* values. MSet::get_matches_estimated()
322 : : // rounds the estimate lazily, but when we merge MSet objects we really
323 : : // want to merge based on the raw estimates.
324 : : //
325 : : // It is also cleaner that a round-trip through serialisation gives you an
326 : : // object which is as close to the original as possible.
327 [ + - ][ + - ]: 10350 : result += encode_length(matches_lower_bound);
328 [ + - ][ + - ]: 10350 : result += encode_length(matches_estimated);
329 [ + - ][ + - ]: 10350 : result += encode_length(matches_upper_bound);
330 [ + - ][ + - ]: 10350 : result += encode_length(uncollapsed_lower_bound);
331 [ + - ][ + - ]: 10350 : result += encode_length(uncollapsed_estimated);
332 [ + - ][ + - ]: 10350 : result += encode_length(uncollapsed_upper_bound);
333 [ + - ][ + - ]: 10350 : result += serialise_double(max_possible);
334 [ + - ][ + - ]: 10350 : result += serialise_double(max_attained);
335 : :
336 [ + - ][ + - ]: 10350 : result += serialise_double(percent_scale_factor);
337 : :
338 [ + - ][ + - ]: 10350 : result += encode_length(items.size());
339 [ + + ]: 118248 : for (auto&& item : items) {
340 [ + - ][ + - ]: 107898 : result += serialise_double(item.get_weight());
341 [ + - ][ + - ]: 107898 : result += encode_length(item.get_docid());
342 [ + - ][ + - ]: 107898 : result += encode_length(item.get_sort_key().size());
343 [ + - ]: 107898 : result += item.get_sort_key();
344 [ + - ][ + - ]: 107898 : result += encode_length(item.get_collapse_key().size());
345 [ + - ]: 107898 : result += item.get_collapse_key();
346 [ + - ][ + - ]: 107898 : result += encode_length(item.get_collapse_count());
347 : : }
348 : :
349 [ + - ]: 10350 : if (stats)
350 [ + - ][ + - ]: 10350 : result += serialise_stats(*stats);
351 : :
352 : 10350 : return result;
353 : : }
354 : :
355 : : void
356 : 10350 : MSet::Internal::unserialise(const char * p, const char * p_end)
357 : : {
358 : 10350 : items.clear();
359 : :
360 [ + - ]: 10350 : decode_length(&p, p_end, first);
361 [ + - ]: 10350 : decode_length(&p, p_end, matches_lower_bound);
362 [ + - ]: 10350 : decode_length(&p, p_end, matches_estimated);
363 [ + - ]: 10350 : decode_length(&p, p_end, matches_upper_bound);
364 [ + - ]: 10350 : decode_length(&p, p_end, uncollapsed_lower_bound);
365 [ + - ]: 10350 : decode_length(&p, p_end, uncollapsed_estimated);
366 [ + - ]: 10350 : decode_length(&p, p_end, uncollapsed_upper_bound);
367 [ + - ]: 10350 : max_possible = unserialise_double(&p, p_end);
368 [ + - ]: 10350 : max_attained = unserialise_double(&p, p_end);
369 : :
370 [ + - ]: 10350 : percent_scale_factor = unserialise_double(&p, p_end);
371 : :
372 : : size_t msize;
373 [ + - ]: 10350 : decode_length(&p, p_end, msize);
374 [ + + ]: 118248 : while (msize-- > 0) {
375 [ + - ]: 107898 : double wt = unserialise_double(&p, p_end);
376 : : Xapian::docid did;
377 [ + - ]: 107898 : decode_length(&p, p_end, did);
378 : : size_t len;
379 [ + - ]: 107898 : decode_length_and_check(&p, p_end, len);
380 [ + - ]: 107898 : string sort_key(p, len);
381 : 107898 : p += len;
382 [ + - ]: 107898 : decode_length_and_check(&p, p_end, len);
383 [ + - ]: 215796 : string key(p, len);
384 : 107898 : p += len;
385 : : Xapian::doccount collapse_cnt;
386 [ + - ]: 107898 : decode_length(&p, p_end, collapse_cnt);
387 : 107898 : items.emplace_back(wt, did, std::move(key), collapse_cnt,
388 [ + - ]: 215796 : std::move(sort_key));
389 : 107898 : }
390 : :
391 [ + - ]: 10350 : if (p != p_end) {
392 [ + - ][ + - ]: 10350 : stats.reset(new Xapian::Weight::Internal());
393 [ + - ][ + - ]: 10350 : unserialise_stats(string(p, p_end - p), *stats);
394 : : }
395 : 10350 : }
396 : :
397 : : string
398 : 124 : MSet::Internal::get_description() const
399 : : {
400 [ + - ]: 124 : string desc = "MSet(matches_lower_bound=";
401 [ + - ][ + - ]: 124 : desc += str(matches_lower_bound);
402 [ + - ]: 124 : desc += ", matches_estimated=";
403 [ + - ][ + - ]: 124 : desc += str(matches_estimated);
404 [ + - ]: 124 : desc += ", matches_upper_bound=";
405 [ + - ][ + - ]: 124 : desc += str(matches_upper_bound);
406 [ - + ]: 124 : if (uncollapsed_lower_bound != matches_lower_bound) {
407 [ # # ]: 0 : desc += ", uncollapsed_lower_bound=";
408 [ # # ][ # # ]: 0 : desc += str(uncollapsed_lower_bound);
409 : : }
410 [ - + ]: 124 : if (uncollapsed_estimated != matches_estimated) {
411 [ # # ]: 0 : desc += ", uncollapsed_estimated=";
412 [ # # ][ # # ]: 0 : desc += str(uncollapsed_estimated);
413 : : }
414 [ - + ]: 124 : if (uncollapsed_upper_bound != matches_upper_bound) {
415 [ # # ]: 0 : desc += ", uncollapsed_upper_bound=";
416 [ # # ][ # # ]: 0 : desc += str(uncollapsed_upper_bound);
417 : : }
418 [ - + ]: 124 : if (first != 0) {
419 [ # # ]: 0 : desc += ", first=";
420 [ # # ][ # # ]: 0 : desc += str(first);
421 : : }
422 [ + + ]: 124 : if (max_possible > 0) {
423 [ + - ]: 112 : desc += ", max_possible=";
424 [ + - ][ + - ]: 112 : desc += str(max_possible);
425 : : }
426 [ + + ]: 124 : if (max_attained > 0) {
427 [ + - ]: 112 : desc += ", max_attained=";
428 [ + - ][ + - ]: 112 : desc += str(max_attained);
429 : : }
430 [ + - ]: 124 : desc += ", [";
431 : 124 : bool comma = false;
432 [ + + ]: 3344 : for (auto&& item : items) {
433 [ + + ]: 3220 : if (comma) {
434 [ + - ]: 3101 : desc += ", ";
435 : : } else {
436 : 119 : comma = true;
437 : : }
438 [ + - ][ + - ]: 3220 : desc += item.get_description();
439 : : }
440 [ + - ]: 124 : desc += "])";
441 : 124 : return desc;
442 : : }
443 : :
444 : : }
|