Branch data Line data Source code
1 : : /** @file brass_values.cc
2 : : * @brief BrassValueManager class
3 : : */
4 : : /* Copyright (C) 2008,2009,2010 Olly Betts
5 : : * Copyright (C) 2008,2009 Lemur Consulting Ltd
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, write to the Free Software
19 : : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 : : */
21 : :
22 : : #include <config.h>
23 : :
24 : : #include "brass_values.h"
25 : :
26 : : #include "brass_cursor.h"
27 : : #include "brass_postlist.h"
28 : : #include "brass_termlist.h"
29 : : #include "debuglog.h"
30 : : #include "document.h"
31 : : #include "pack.h"
32 : :
33 : : #include "xapian/error.h"
34 : : #include "xapian/valueiterator.h"
35 : :
36 : : #include <algorithm>
37 : : #include "autoptr.h"
38 : :
39 : : using namespace Brass;
40 : : using namespace std;
41 : :
42 : : // FIXME:
43 : : // * put the "used slots" entry in the same termlist tag as the terms?
44 : : // * multi-values?
45 : : // * values named instead of numbered?
46 : :
47 : : /** Generate a key for the "used slots" data. */
48 : : inline string
49 : 450809 : make_slot_key(Xapian::docid did)
50 : : {
51 : : LOGCALL_STATIC(DB, string, "make_slot_key", did);
52 : : // Add an extra character so that it can't clash with a termlist entry key
53 : : // and will sort just after the corresponding termlist entry key.
54 : : // FIXME: should we store this in the *same entry* as the list of terms?
55 : 450809 : string key;
56 : 450809 : pack_uint_preserving_sort(key, did);
57 : 450809 : key += '\0';
58 : 0 : RETURN(key);
59 : : }
60 : :
61 : : /** Generate a key for a value statistics item. */
62 : : inline string
63 : 665176 : make_valuestats_key(Xapian::valueno slot)
64 : : {
65 : : LOGCALL_STATIC(DB, string, "make_valuestats_key", slot);
66 : 665176 : string key("\0\xd0", 2);
67 : 665176 : pack_uint_last(key, slot);
68 : 0 : RETURN(key);
69 : : }
70 : :
71 : : void
72 : 1682301 : ValueChunkReader::assign(const char * p_, size_t len, Xapian::docid did_)
73 : : {
74 : 1682301 : p = p_;
75 : 1682301 : end = p_ + len;
76 : 1682301 : did = did_;
77 [ - + ]: 1682301 : if (!unpack_string(&p, end, value))
78 : 0 : throw Xapian::DatabaseCorruptError("Failed to unpack first value");
79 : 1682301 : }
80 : :
81 : : void
82 : 45048 : ValueChunkReader::next()
83 : : {
84 [ + + ]: 45048 : if (p == end) {
85 : 1070 : p = NULL;
86 : 1070 : return;
87 : : }
88 : :
89 : : Xapian::docid delta;
90 [ - + ]: 43978 : if (!unpack_uint(&p, end, &delta))
91 : 0 : throw Xapian::DatabaseCorruptError("Failed to unpack streamed value docid");
92 : 43978 : did += delta + 1;
93 [ - + ]: 43978 : if (!unpack_string(&p, end, value))
94 : 45048 : throw Xapian::DatabaseCorruptError("Failed to unpack streamed value");
95 : : }
96 : :
97 : : void
98 : 1721228 : ValueChunkReader::skip_to(Xapian::docid target)
99 : : {
100 [ + - ][ + + ]: 1721228 : if (p == NULL || target <= did)
101 : 63908 : return;
102 : :
103 : : size_t value_len;
104 [ + + ]: 347523566 : while (p != end) {
105 : : // Get the next docid
106 : : Xapian::docid delta;
107 [ - + ]: 347510540 : if (rare(!unpack_uint(&p, end, &delta)))
108 : 0 : throw Xapian::DatabaseCorruptError("Failed to unpack streamed value docid");
109 : 347510540 : did += delta + 1;
110 : :
111 : : // Get the length of the string
112 [ - + ]: 347510540 : if (rare(!unpack_uint(&p, end, &value_len))) {
113 : 0 : throw Xapian::DatabaseCorruptError("Failed to unpack streamed value length");
114 : : }
115 : :
116 : : // Check that it's not too long
117 [ - + ]: 347510540 : if (rare(value_len > size_t(end - p))) {
118 : 0 : throw Xapian::DatabaseCorruptError("Failed to unpack streamed value");
119 : : }
120 : :
121 : : // Assign the value and return only if we've reached the target
122 [ + + ]: 347510540 : if (did >= target) {
123 : 1644294 : value.assign(p, value_len);
124 : 1644294 : p += value_len;
125 : 1644294 : return;
126 : : }
127 : 345866246 : p += value_len;
128 : : }
129 : 1721228 : p = NULL;
130 : : }
131 : :
132 : : void
133 : 287909 : BrassValueManager::add_value(Xapian::docid did, Xapian::valueno slot,
134 : : const string & val)
135 : : {
136 : 287909 : map<Xapian::valueno, map<Xapian::docid, string> >::iterator i;
137 : 287909 : i = changes.find(slot);
138 [ + + ]: 287909 : if (i == changes.end()) {
139 : 166051 : i = changes.insert(make_pair(slot, map<Xapian::docid, string>())).first;
140 : : }
141 : 287909 : i->second[did] = val;
142 : 287909 : }
143 : :
144 : : void
145 : 8153 : BrassValueManager::remove_value(Xapian::docid did, Xapian::valueno slot)
146 : : {
147 : 8153 : map<Xapian::valueno, map<Xapian::docid, string> >::iterator i;
148 : 8153 : i = changes.find(slot);
149 [ + + ]: 8153 : if (i == changes.end()) {
150 : 113 : i = changes.insert(make_pair(slot, map<Xapian::docid, string>())).first;
151 : : }
152 : 8153 : i->second[did] = string();
153 : 8153 : }
154 : :
155 : : Xapian::docid
156 : 1668073 : BrassValueManager::get_chunk_containing_did(Xapian::valueno slot,
157 : : Xapian::docid did,
158 : : string &chunk) const
159 : : {
160 : : LOGCALL(DB, Xapian::docid, "BrassValueManager::get_chunk_containing_did", slot | did | chunk);
161 : 1668073 : AutoPtr<BrassCursor> cursor(postlist_table->cursor_get());
162 [ - + ]: 1668071 : if (!cursor.get()) return 0;
163 : :
164 : 1668071 : bool exact = cursor->find_entry(make_valuechunk_key(slot, did));
165 [ + + ]: 1668071 : if (!exact) {
166 : : // If we didn't find a chunk starting with docid did, then we need
167 : : // to check if the chunk contains did.
168 : 1604499 : const char * p = cursor->current_key.data();
169 : 1604499 : const char * end = p + cursor->current_key.size();
170 : :
171 : : // Check that it is a value stream chunk.
172 [ + + + - ]: 1604499 : if (end - p < 2 || *p++ != '\0' || *p++ != '\xd8') return 0;
[ - + ][ + + ]
173 : :
174 : : // Check that it's for the right value slot.
175 : : Xapian::valueno v;
176 [ - + ]: 1603496 : if (!unpack_uint(&p, end, &v)) {
177 : 0 : throw Xapian::DatabaseCorruptError("Bad value key");
178 : : }
179 [ + + ]: 1603496 : if (v != slot) return 0;
180 : :
181 : : // And get the first docid for the chunk so we can return it.
182 [ + - ][ - + ]: 1603489 : if (!unpack_uint_preserving_sort(&p, end, &did) || p != end) {
[ - + ]
183 : 0 : throw Xapian::DatabaseCorruptError("Bad value key");
184 : : }
185 : : }
186 : :
187 : 1667061 : cursor->read_tag();
188 : 1667061 : swap(chunk, cursor->current_tag);
189 : :
190 : 1668071 : return did;
191 : : }
192 : :
193 : : static const size_t CHUNK_SIZE_THRESHOLD = 2000;
194 : :
195 : : static const Xapian::docid MAX_DOCID = static_cast<Xapian::docid>(-1);
196 : :
197 : : namespace Brass {
198 : :
199 : : class ValueUpdater {
200 : : BrassPostListTable * table;
201 : :
202 : : Xapian::valueno slot;
203 : :
204 : : string ctag;
205 : :
206 : : ValueChunkReader reader;
207 : :
208 : : string tag;
209 : :
210 : : Xapian::docid prev_did;
211 : :
212 : : Xapian::docid first_did;
213 : :
214 : : Xapian::docid new_first_did;
215 : :
216 : : Xapian::docid last_allowed_did;
217 : :
218 : 314111 : void append_to_stream(Xapian::docid did, const string & value) {
219 : : Assert(did);
220 [ + + ]: 314111 : if (tag.empty()) {
221 : 167428 : new_first_did = did;
222 : : } else {
223 : : AssertRel(did,>,prev_did);
224 : 146683 : pack_uint(tag, did - prev_did - 1);
225 : : }
226 : 314111 : prev_did = did;
227 : 314111 : pack_string(tag, value);
228 [ + + ]: 314111 : if (tag.size() >= CHUNK_SIZE_THRESHOLD) write_tag();
229 : 314111 : }
230 : :
231 : 167518 : void write_tag() {
232 : : // If the first docid has changed, delete the old entry.
233 [ + + ][ + + ]: 167518 : if (first_did && new_first_did != first_did) {
234 : 128 : table->del(make_valuechunk_key(slot, first_did));
235 : : }
236 [ + + ]: 167518 : if (!tag.empty()) {
237 : 167428 : table->add(make_valuechunk_key(slot, new_first_did), tag);
238 : : }
239 : 167518 : first_did = 0;
240 : 167518 : tag.resize(0);
241 : 167518 : }
242 : :
243 : : public:
244 : 166163 : ValueUpdater(BrassPostListTable * table_, Xapian::valueno slot_)
245 : 166163 : : table(table_), slot(slot_), first_did(0), last_allowed_did(0) { }
246 : :
247 : 166163 : ~ValueUpdater() {
248 [ + + ]: 168264 : while (!reader.at_end()) {
249 : : // FIXME: use skip_to and some splicing magic instead?
250 : 2101 : append_to_stream(reader.get_docid(), reader.get_value());
251 : 2101 : reader.next();
252 : : }
253 : 166163 : write_tag();
254 : 166163 : }
255 : :
256 : 288442 : void update(Xapian::docid did, const string & value) {
257 [ + + ][ + + ]: 288442 : if (last_allowed_did && did > last_allowed_did) {
258 : : // The next change needs to go in a later existing chunk than the
259 : : // one we're currently updating, so we copy over the rest of the
260 : : // entries from the current chunk, write out the updated chunk and
261 : : // drop through to the case below will read in that later chunk.
262 : : // FIXME: use some string splicing magic instead of this loop.
263 [ + + ]: 3308 : while (!reader.at_end()) {
264 : : // last_allowed_did should be an upper bound for this chunk.
265 : : AssertRel(reader.get_docid(),<=,last_allowed_did);
266 : 3259 : append_to_stream(reader.get_docid(), reader.get_value());
267 : 3259 : reader.next();
268 : : }
269 : 49 : write_tag();
270 : 49 : last_allowed_did = 0;
271 : : }
272 [ + + ]: 288442 : if (last_allowed_did == 0) {
273 : 166212 : last_allowed_did = MAX_DOCID;
274 : : Assert(tag.empty());
275 : 166212 : new_first_did = 0;
276 : 166212 : AutoPtr<BrassCursor> cursor(table->cursor_get());
277 [ + + ]: 166212 : if (cursor->find_entry(make_valuechunk_key(slot, did))) {
278 : : // We found an exact match, so the first docid is the one
279 : : // we looked for.
280 : 148 : first_did = did;
281 : : } else {
282 : : Assert(!cursor->after_end());
283 : : // Otherwise we need to unpack it from the key we found.
284 : : // We may have found a non-value-chunk entry in which case
285 : : // docid_from_key() returns 0.
286 : 166064 : first_did = docid_from_key(slot, cursor->current_key);
287 : : }
288 : :
289 : : // If there are no further chunks, then the last docid that can go
290 : : // in this chunk is the highest valid docid. If there are further
291 : : // chunks then it's one less than the first docid of the next
292 : : // chunk.
293 [ + + ]: 166212 : if (first_did) {
294 : : // We found a value chunk.
295 : 172 : cursor->read_tag();
296 : : // FIXME:swap(cursor->current_tag, ctag);
297 : 172 : ctag = cursor->current_tag;
298 : 172 : reader.assign(ctag.data(), ctag.size(), first_did);
299 : : }
300 [ + + ]: 166212 : if (cursor->next()) {
301 : 166190 : const string & key = cursor->current_key;
302 : 166190 : Xapian::docid next_first_did = docid_from_key(slot, key);
303 [ + + ]: 166190 : if (next_first_did) last_allowed_did = next_first_did - 1;
304 : : Assert(last_allowed_did);
305 : : AssertRel(last_allowed_did,>=,first_did);
306 : 166212 : }
307 : : }
308 : :
309 : : // Copy over entries until we get to the one we want to
310 : : // add/modify/delete.
311 : : // FIXME: use skip_to and some splicing magic instead?
312 [ + + ][ + + ]: 312072 : while (!reader.at_end() && reader.get_docid() < did) {
[ + + ]
313 : 23630 : append_to_stream(reader.get_docid(), reader.get_value());
314 : 23630 : reader.next();
315 : : }
316 [ + + ][ + - ]: 288442 : if (!reader.at_end() && reader.get_docid() == did) reader.next();
[ + + ]
317 [ + + ]: 288442 : if (!value.empty()) {
318 : : // Add/update entry for did.
319 : 285121 : append_to_stream(did, value);
320 : : }
321 : 288442 : }
322 : : };
323 : :
324 : : }
325 : :
326 : : void
327 : 529 : BrassValueManager::merge_changes()
328 : : {
329 [ + - ]: 529 : if (termlist_table->is_open()) {
330 : 529 : map<Xapian::docid, string>::const_iterator i;
331 [ + + ]: 45931 : for (i = slots.begin(); i != slots.end(); ++i) {
332 : 45402 : const string & enc = i->second;
333 : 45402 : string key = make_slot_key(i->first);
334 [ + + ]: 45402 : if (!enc.empty()) {
335 : 42366 : termlist_table->add(key, i->second);
336 : : } else {
337 : 3036 : termlist_table->del(key);
338 : : }
339 : : }
340 : 529 : slots.clear();
341 : : }
342 : :
343 : : {
344 : 529 : map<Xapian::valueno, map<Xapian::docid, string> >::const_iterator i;
345 [ + + ]: 166692 : for (i = changes.begin(); i != changes.end(); ++i) {
346 : 166163 : Xapian::valueno slot = i->first;
347 : 166163 : Brass::ValueUpdater updater(postlist_table, slot);
348 : 166163 : const map<Xapian::docid, string> & slot_changes = i->second;
349 : 166163 : map<Xapian::docid, string>::const_iterator j;
350 [ + + ]: 454605 : for (j = slot_changes.begin(); j != slot_changes.end(); ++j) {
351 : 288442 : updater.update(j->first, j->second);
352 : : }
353 : : }
354 : 529 : changes.clear();
355 : : }
356 : 529 : }
357 : :
358 : : void
359 : 77510 : BrassValueManager::add_document(Xapian::docid did, const Xapian::Document &doc,
360 : : map<Xapian::valueno, ValueStats> & value_stats)
361 : : {
362 : : // FIXME: Use BitWriter and interpolative coding? Or is it not worthwhile
363 : : // for this?
364 : 77510 : string slots_used;
365 : 77510 : Xapian::valueno prev_slot = static_cast<Xapian::valueno>(-1);
366 : 77510 : Xapian::ValueIterator it = doc.values_begin();
367 [ + + ]: 365419 : while (it != doc.values_end()) {
368 : 287909 : Xapian::valueno slot = it.get_valueno();
369 : 287909 : string value = *it;
370 : :
371 : : // Update the statistics.
372 : 287909 : std::pair<map<Xapian::valueno, ValueStats>::iterator, bool> i;
373 [ - + ]: 287909 : i = value_stats.insert(make_pair(slot, ValueStats()));
374 : 287909 : ValueStats & stats = i.first->second;
375 [ + + ]: 287909 : if (i.second) {
376 : : // There were no statistics stored already, so read them.
377 : 166051 : get_value_stats(slot, stats);
378 : : }
379 : :
380 : : // Now, modify the stored statistics.
381 [ + + ]: 287909 : if ((stats.freq)++ == 0) {
382 : : // If the value count was previously zero, set the upper and lower
383 : : // bounds to the newly added value.
384 : 166058 : stats.lower_bound = value;
385 : 166058 : stats.upper_bound = value;
386 : : } else {
387 : : // Otherwise, simply make sure they reflect the new value.
388 [ + + ]: 121851 : if (value < stats.lower_bound) {
389 : 535 : stats.lower_bound = value;
390 [ + + ]: 121316 : } else if (value > stats.upper_bound) {
391 : 847 : stats.upper_bound = value;
392 : : }
393 : : }
394 : :
395 : 287909 : add_value(did, slot, value);
396 [ + - ]: 287909 : if (termlist_table->is_open()) {
397 : 287909 : pack_uint(slots_used, slot - prev_slot - 1);
398 : 287909 : prev_slot = slot;
399 : : }
400 : 287909 : ++it;
401 : : }
402 [ + + ][ + + ]: 77510 : if (slots_used.empty() && slots.find(did) == slots.end()) {
[ + + ]
403 : : // Adding a new document with no values which we didn't just remove.
404 : : } else {
405 : 48741 : swap(slots[did], slots_used);
406 : 77510 : }
407 : 77510 : }
408 : :
409 : : void
410 : 19080 : BrassValueManager::delete_document(Xapian::docid did,
411 : : map<Xapian::valueno, ValueStats> & value_stats)
412 : : {
413 : : Assert(termlist_table->is_open());
414 : 19080 : map<Xapian::docid, string>::iterator it = slots.find(did);
415 : 19080 : string s;
416 [ + + ]: 19080 : if (it != slots.end()) {
417 : 3373 : swap(s, it->second);
418 : : } else {
419 : : // Get from table, making a swift exit if this document has no values.
420 [ + + ]: 15707 : if (!termlist_table->get_exact_entry(make_slot_key(did), s)) return;
421 : 5179 : slots.insert(make_pair(did, string()));
422 : : }
423 : 8552 : const char * p = s.data();
424 : 8552 : const char * end = p + s.size();
425 : 8552 : Xapian::valueno prev_slot = static_cast<Xapian::valueno>(-1);
426 [ + + ]: 16705 : while (p != end) {
427 : : Xapian::valueno slot;
428 [ - + ]: 8153 : if (!unpack_uint(&p, end, &slot)) {
429 : 0 : throw Xapian::DatabaseCorruptError("Value slot encoding corrupt");
430 : : }
431 : 8153 : slot += prev_slot + 1;
432 : 8153 : prev_slot = slot;
433 : :
434 : 8153 : std::pair<map<Xapian::valueno, ValueStats>::iterator, bool> i;
435 [ - + ]: 8153 : i = value_stats.insert(make_pair(slot, ValueStats()));
436 : 8153 : ValueStats & stats = i.first->second;
437 [ + + ]: 8153 : if (i.second) {
438 : : // There were no statistics stored already, so read them.
439 : 113 : get_value_stats(slot, stats);
440 : : }
441 : :
442 : : // Now, modify the stored statistics.
443 : : AssertRelParanoid(stats.freq, >, 0);
444 [ + + ]: 8153 : if (--(stats.freq) == 0) {
445 : 78 : stats.lower_bound.resize(0);
446 : 78 : stats.upper_bound.resize(0);
447 : : }
448 : :
449 : 8153 : remove_value(did, slot);
450 : 19080 : }
451 : : }
452 : :
453 : : void
454 : 9189 : BrassValueManager::replace_document(Xapian::docid did,
455 : : const Xapian::Document &doc,
456 : : map<Xapian::valueno, ValueStats> & value_stats)
457 : : {
458 : : // Load the values into the document from the database, if they haven't
459 : : // been already. (If we don't do this before deleting the old values,
460 : : // replacing a document with itself will lose the values.)
461 : 9189 : doc.internal->need_values();
462 : 9189 : delete_document(did, value_stats);
463 : 9189 : add_document(did, doc, value_stats);
464 : 9189 : }
465 : :
466 : : string
467 : 1675969 : BrassValueManager::get_value(Xapian::docid did, Xapian::valueno slot) const
468 : : {
469 : 1675969 : map<Xapian::valueno, map<Xapian::docid, string> >::const_iterator i;
470 : 1675969 : i = changes.find(slot);
471 [ + + ]: 1675969 : if (i != changes.end()) {
472 : 18551 : map<Xapian::docid, string>::const_iterator j;
473 : 18551 : j = i->second.find(did);
474 [ + + ]: 18551 : if (j != i->second.end()) return j->second;
475 : : }
476 : :
477 : : // Read it from the table.
478 : 1668073 : string chunk;
479 : : Xapian::docid first_did;
480 : 1668073 : first_did = get_chunk_containing_did(slot, did, chunk);
481 [ + + ]: 1668071 : if (first_did == 0) return string();
482 : :
483 : 1667061 : ValueChunkReader reader(chunk.data(), chunk.size(), first_did);
484 : 1667061 : reader.skip_to(did);
485 [ + + + + ]: 1667061 : if (reader.at_end() || reader.get_docid() != did) return string();
[ + + ]
486 : 1675969 : return reader.get_value();
487 : : }
488 : :
489 : : void
490 : 172470 : BrassValueManager::get_all_values(map<Xapian::valueno, string> & values,
491 : : Xapian::docid did) const
492 : : {
493 : : Assert(values.empty());
494 [ - + ]: 172470 : if (!termlist_table->is_open())
495 : 0 : throw Xapian::FeatureUnavailableError("Database has no termlist");
496 : 172470 : map<Xapian::docid, string>::const_iterator i = slots.find(did);
497 : 172470 : string s;
498 [ + + ]: 172470 : if (i != slots.end()) {
499 : 8178 : s = i->second;
500 : : } else {
501 : : // Get from table.
502 [ + + ]: 164292 : if (!termlist_table->get_exact_entry(make_slot_key(did), s)) return;
503 : : }
504 : 157780 : const char * p = s.data();
505 : 157780 : const char * end = p + s.size();
506 : 157780 : Xapian::valueno prev_slot = static_cast<Xapian::valueno>(-1);
507 [ + + ]: 1785926 : while (p != end) {
508 : : Xapian::valueno slot;
509 [ - + ]: 1628146 : if (!unpack_uint(&p, end, &slot)) {
510 : 0 : throw Xapian::DatabaseCorruptError("Value slot encoding corrupt");
511 : : }
512 : 1628146 : slot += prev_slot + 1;
513 : 1628146 : prev_slot = slot;
514 : 1628146 : values.insert(make_pair(slot, get_value(did, slot)));
515 : 172470 : }
516 : : }
517 : :
518 : : void
519 : 261 : BrassValueManager::get_value_stats(Xapian::valueno slot) const
520 : : {
521 : : LOGCALL_VOID(DB, "BrassValueManager::get_value_stats", slot);
522 : : // Invalidate the cache first in case an exception is thrown.
523 : 261 : mru_valno = Xapian::BAD_VALUENO;
524 : 261 : get_value_stats(slot, mru_valstats);
525 : 261 : mru_valno = slot;
526 : 261 : }
527 : :
528 : : void
529 : 166425 : BrassValueManager::get_value_stats(Xapian::valueno slot, ValueStats & stats) const
530 : : {
531 : : LOGCALL_VOID(DB, "BrassValueManager::get_value_stats", slot | Literal("[stats]"));
532 : : // Invalidate the cache first in case an exception is thrown.
533 : 166425 : mru_valno = Xapian::BAD_VALUENO;
534 : :
535 : 166425 : string tag;
536 [ + + ]: 166425 : if (postlist_table->get_exact_entry(make_valuestats_key(slot), tag)) {
537 : 347 : const char * pos = tag.data();
538 : 347 : const char * end = pos + tag.size();
539 : :
540 [ - + ]: 347 : if (!unpack_uint(&pos, end, &(stats.freq))) {
541 [ # # ]: 0 : if (*pos == 0) throw Xapian::DatabaseCorruptError("Incomplete stats item in value table");
542 : 0 : throw Xapian::RangeError("Frequency statistic in value table is too large");
543 : : }
544 [ - + ]: 347 : if (!unpack_string(&pos, end, stats.lower_bound)) {
545 [ # # ]: 0 : if (*pos == 0) throw Xapian::DatabaseCorruptError("Incomplete stats item in value table");
546 : 0 : throw Xapian::RangeError("Lower bound in value table is too large");
547 : : }
548 : 347 : size_t len = end - pos;
549 [ + + ]: 347 : if (len == 0) {
550 : 132 : stats.upper_bound = stats.lower_bound;
551 : : } else {
552 : 215 : stats.upper_bound.assign(pos, len);
553 : : }
554 : : } else {
555 : 166078 : stats.clear();
556 : : }
557 : :
558 : 166425 : mru_valno = slot;
559 : 166425 : }
560 : :
561 : : void
562 : 1138 : BrassValueManager::set_value_stats(map<Xapian::valueno, ValueStats> & value_stats)
563 : : {
564 : : LOGCALL_VOID(DB, "BrassValueManager::set_value_stats", value_stats);
565 : 1138 : map<Xapian::valueno, ValueStats>::const_iterator i;
566 [ + + ]: 167301 : for (i = value_stats.begin(); i != value_stats.end(); ++i) {
567 : 166163 : string key = make_valuestats_key(i->first);
568 : 166163 : const ValueStats & stats = i->second;
569 [ + + ]: 166163 : if (stats.freq != 0) {
570 : 166106 : string new_value;
571 : 166106 : pack_uint(new_value, stats.freq);
572 : 166106 : pack_string(new_value, stats.lower_bound);
573 : : // We don't store or count empty values, so neither of the bounds
574 : : // can be empty. So we can safely store an empty upper bound when
575 : : // the bounds are equal.
576 [ + + ]: 166106 : if (stats.lower_bound != stats.upper_bound)
577 : 549 : new_value += stats.upper_bound;
578 : 166106 : postlist_table->add(key, new_value);
579 : : } else {
580 : 57 : postlist_table->del(key);
581 : : }
582 : : }
583 : 1138 : value_stats.clear();
584 : 1138 : mru_valno = Xapian::BAD_VALUENO;
585 : 1138 : }
|