xapian-core  1.4.19
glass_values.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2008,2009,2010,2011,2012,2016,2017 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 "glass_values.h"
25 
26 #include "glass_cursor.h"
27 #include "glass_postlist.h"
28 #include "glass_termlist.h"
29 #include "debuglog.h"
30 #include "backends/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 Glass;
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 
48 static inline string
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  string key;
56  pack_uint_preserving_sort(key, did);
57  key += '\0';
58  RETURN(key);
59 }
60 
62 static inline string
64 {
65  LOGCALL_STATIC(DB, string, "make_valuestats_key", slot);
66  string key("\0\xd0", 2);
67  pack_uint_last(key, slot);
68  RETURN(key);
69 }
70 
71 void
72 ValueChunkReader::assign(const char * p_, size_t len, Xapian::docid did_)
73 {
74  p = p_;
75  end = p_ + len;
76  did = did_;
77  if (!unpack_string(&p, end, value))
78  throw Xapian::DatabaseCorruptError("Failed to unpack first value");
79 }
80 
81 void
83 {
84  if (p == end) {
85  p = NULL;
86  return;
87  }
88 
89  Xapian::docid delta;
90  if (!unpack_uint(&p, end, &delta))
91  throw Xapian::DatabaseCorruptError("Failed to unpack streamed value docid");
92  did += delta + 1;
93  if (!unpack_string(&p, end, value))
94  throw Xapian::DatabaseCorruptError("Failed to unpack streamed value");
95 }
96 
97 void
99 {
100  if (p == NULL || target <= did)
101  return;
102 
103  size_t value_len;
104  while (p != end) {
105  // Get the next docid
106  Xapian::docid delta;
107  if (rare(!unpack_uint(&p, end, &delta)))
108  throw Xapian::DatabaseCorruptError("Failed to unpack streamed value docid");
109  did += delta + 1;
110 
111  // Get the length of the string
112  if (rare(!unpack_uint(&p, end, &value_len))) {
113  throw Xapian::DatabaseCorruptError("Failed to unpack streamed value length");
114  }
115 
116  // Check that it's not too long
117  if (rare(value_len > size_t(end - p))) {
118  throw Xapian::DatabaseCorruptError("Failed to unpack streamed value");
119  }
120 
121  // Assign the value and return only if we've reached the target
122  if (did >= target) {
123  value.assign(p, value_len);
124  p += value_len;
125  return;
126  }
127  p += value_len;
128  }
129  p = NULL;
130 }
131 
132 void
134  const string & val)
135 {
136  auto i = changes.find(slot);
137  if (i == changes.end()) {
138  i = changes.insert(make_pair(slot, map<Xapian::docid, string>())).first;
139  }
140  i->second[did] = val;
141 }
142 
143 void
145 {
146  auto i = changes.find(slot);
147  if (i == changes.end()) {
148  i = changes.insert(make_pair(slot, map<Xapian::docid, string>())).first;
149  }
150  i->second[did] = string();
151 }
152 
155  Xapian::docid did,
156  string &chunk) const
157 {
158  LOGCALL(DB, Xapian::docid, "GlassValueManager::get_chunk_containing_did", slot | did | chunk);
159  if (!cursor.get())
160  cursor.reset(postlist_table->cursor_get());
161  if (!cursor.get()) RETURN(0);
162 
163  bool exact = cursor->find_entry(make_valuechunk_key(slot, did));
164  if (!exact) {
165  // If we didn't find a chunk starting with docid did, then we need
166  // to check if the chunk contains did.
167  const char * p = cursor->current_key.data();
168  const char * end = p + cursor->current_key.size();
169 
170  // Check that it is a value stream chunk.
171  if (end - p < 2 || *p++ != '\0' || *p++ != '\xd8') RETURN(0);
172 
173  // Check that it's for the right value slot.
174  Xapian::valueno v;
175  if (!unpack_uint(&p, end, &v)) {
176  throw Xapian::DatabaseCorruptError("Bad value key");
177  }
178  if (v != slot) RETURN(0);
179 
180  // And get the first docid for the chunk so we can return it.
181  if (!unpack_uint_preserving_sort(&p, end, &did) || p != end) {
182  throw Xapian::DatabaseCorruptError("Bad value key");
183  }
184  }
185 
186  cursor->read_tag();
187  swap(chunk, cursor->current_tag);
188 
189  RETURN(did);
190 }
191 
192 static const size_t CHUNK_SIZE_THRESHOLD = 2000;
193 
194 namespace Glass {
195 
198 
200 
201  string ctag;
202 
204 
205  string tag;
206 
208 
210 
212 
214 
215  void append_to_stream(Xapian::docid did, const string & value) {
216  Assert(did);
217  if (tag.empty()) {
218  new_first_did = did;
219  } else {
220  AssertRel(did,>,prev_did);
221  pack_uint(tag, did - prev_did - 1);
222  }
223  prev_did = did;
224  pack_string(tag, value);
225  if (tag.size() >= CHUNK_SIZE_THRESHOLD) write_tag();
226  }
227 
228  void write_tag() {
229  // If the first docid has changed, delete the old entry.
230  if (first_did && new_first_did != first_did) {
231  table->del(make_valuechunk_key(slot, first_did));
232  }
233  if (!tag.empty()) {
234  table->add(make_valuechunk_key(slot, new_first_did), tag);
235  }
236  first_did = 0;
237  tag.resize(0);
238  }
239 
240  public:
242  : table(table_), slot(slot_), first_did(0), last_allowed_did(0) { }
243 
245  while (!reader.at_end()) {
246  // FIXME: use skip_to and some splicing magic instead?
247  append_to_stream(reader.get_docid(), reader.get_value());
248  reader.next();
249  }
250  write_tag();
251  }
252 
253  void update(Xapian::docid did, const string & value) {
254  if (last_allowed_did && did > last_allowed_did) {
255  // The next change needs to go in a later existing chunk than the
256  // one we're currently updating, so we copy over the rest of the
257  // entries from the current chunk, write out the updated chunk and
258  // drop through to the case below will read in that later chunk.
259  // FIXME: use some string splicing magic instead of this loop.
260  while (!reader.at_end()) {
261  // last_allowed_did should be an upper bound for this chunk.
262  AssertRel(reader.get_docid(),<=,last_allowed_did);
263  append_to_stream(reader.get_docid(), reader.get_value());
264  reader.next();
265  }
266  write_tag();
267  last_allowed_did = 0;
268  }
269  if (last_allowed_did == 0) {
270  last_allowed_did = GLASS_MAX_DOCID;
271  Assert(tag.empty());
272  new_first_did = 0;
273  AutoPtr<GlassCursor> cursor(table->cursor_get());
274  if (cursor->find_entry(make_valuechunk_key(slot, did))) {
275  // We found an exact match, so the first docid is the one
276  // we looked for.
277  first_did = did;
278  } else {
279  Assert(!cursor->after_end());
280  // Otherwise we need to unpack it from the key we found.
281  // We may have found a non-value-chunk entry in which case
282  // docid_from_key() returns 0.
283  first_did = docid_from_key(slot, cursor->current_key);
284  }
285 
286  // If there are no further chunks, then the last docid that can go
287  // in this chunk is the highest valid docid. If there are further
288  // chunks then it's one less than the first docid of the next
289  // chunk.
290  if (first_did) {
291  // We found a value chunk.
292  cursor->read_tag();
293  // FIXME:swap(cursor->current_tag, ctag);
294  ctag = cursor->current_tag;
295  reader.assign(ctag.data(), ctag.size(), first_did);
296  }
297  if (cursor->next()) {
298  const string & key = cursor->current_key;
299  Xapian::docid next_first_did = docid_from_key(slot, key);
300  if (next_first_did) last_allowed_did = next_first_did - 1;
301  Assert(last_allowed_did);
302  AssertRel(last_allowed_did,>=,first_did);
303  }
304  }
305 
306  // Copy over entries until we get to the one we want to
307  // add/modify/delete.
308  // FIXME: use skip_to and some splicing magic instead?
309  while (!reader.at_end() && reader.get_docid() < did) {
310  append_to_stream(reader.get_docid(), reader.get_value());
311  reader.next();
312  }
313  if (!reader.at_end() && reader.get_docid() == did) reader.next();
314  if (!value.empty()) {
315  // Add/update entry for did.
316  append_to_stream(did, value);
317  }
318  }
319 };
320 
321 }
322 
323 void
325 {
326  if (termlist_table->is_open()) {
327  for (auto i : slots) {
328  string key = make_slot_key(i.first);
329  const string& enc = i.second;
330  if (!enc.empty()) {
331  termlist_table->add(key, enc);
332  } else {
333  termlist_table->del(key);
334  }
335  }
336  slots.clear();
337  }
338 
339  for (auto i : changes) {
340  Xapian::valueno slot = i.first;
341  Glass::ValueUpdater updater(postlist_table, slot);
342  const map<Xapian::docid, string>& slot_changes = i.second;
343  for (auto j : slot_changes) {
344  updater.update(j.first, j.second);
345  }
346  }
347  changes.clear();
348 }
349 
350 void
352  map<Xapian::valueno, ValueStats> & value_stats)
353 {
354  // FIXME: Use BitWriter and interpolative coding? Or is it not worthwhile
355  // for this?
356  string slots_used;
357  Xapian::valueno prev_slot = static_cast<Xapian::valueno>(-1);
359  while (it != doc.values_end()) {
360  Xapian::valueno slot = it.get_valueno();
361  string value = *it;
362  Assert(!value.empty());
363 
364  // Update the statistics.
365  std::pair<map<Xapian::valueno, ValueStats>::iterator, bool> i;
366  i = value_stats.insert(make_pair(slot, ValueStats()));
367  ValueStats & stats = i.first->second;
368  if (i.second) {
369  // There were no statistics stored already, so read them.
370  get_value_stats(slot, stats);
371  }
372 
373  // Now, modify the stored statistics.
374  if ((stats.freq)++ == 0) {
375  // If the value count was previously zero, set the upper and lower
376  // bounds to the newly added value.
377  stats.lower_bound = value;
378  stats.upper_bound = value;
379  } else {
380  // Otherwise, simply make sure they reflect the new value.
381  if (value < stats.lower_bound) {
382  stats.lower_bound = value;
383  } else if (value > stats.upper_bound) {
384  stats.upper_bound = value;
385  }
386  }
387 
388  add_value(did, slot, value);
389  if (termlist_table->is_open()) {
390  pack_uint(slots_used, slot - prev_slot - 1);
391  prev_slot = slot;
392  }
393  ++it;
394  }
395  if (slots_used.empty() && slots.find(did) == slots.end()) {
396  // Adding a new document with no values which we didn't just remove.
397  } else {
398  swap(slots[did], slots_used);
399  }
400 }
401 
402 void
404  map<Xapian::valueno, ValueStats> & value_stats)
405 {
406  Assert(termlist_table->is_open());
407  map<Xapian::docid, string>::iterator it = slots.find(did);
408  string s;
409  if (it != slots.end()) {
410  swap(s, it->second);
411  } else {
412  // Get from table, making a swift exit if this document has no values.
413  if (!termlist_table->get_exact_entry(make_slot_key(did), s)) return;
414  slots.insert(make_pair(did, string()));
415  }
416  const char * p = s.data();
417  const char * end = p + s.size();
418  Xapian::valueno prev_slot = static_cast<Xapian::valueno>(-1);
419  while (p != end) {
420  Xapian::valueno slot;
421  if (!unpack_uint(&p, end, &slot)) {
422  throw Xapian::DatabaseCorruptError("Value slot encoding corrupt");
423  }
424  slot += prev_slot + 1;
425  prev_slot = slot;
426 
427  std::pair<map<Xapian::valueno, ValueStats>::iterator, bool> i;
428  i = value_stats.insert(make_pair(slot, ValueStats()));
429  ValueStats & stats = i.first->second;
430  if (i.second) {
431  // There were no statistics stored already, so read them.
432  get_value_stats(slot, stats);
433  }
434 
435  // Now, modify the stored statistics.
436  AssertRelParanoid(stats.freq, >, 0);
437  if (--(stats.freq) == 0) {
438  stats.lower_bound.resize(0);
439  stats.upper_bound.resize(0);
440  }
441 
442  remove_value(did, slot);
443  }
444 }
445 
446 void
448  const Xapian::Document &doc,
449  map<Xapian::valueno, ValueStats> & value_stats)
450 {
451  if (doc.get_docid() == did) {
452  // If we're replacing a document with itself, but the optimisation for
453  // this higher up hasn't kicked in (e.g. because we've added/replaced
454  // a document since this one was read) and the values haven't changed,
455  // then the call to delete_document() below will remove the values
456  // before the subsequent add_document() can read them.
457  //
458  // The simplest way to handle this is to force the document to read its
459  // values, which we only need to do this is the docid matches. Note
460  // that this check can give false positives as we don't also check the
461  // database, so for example replacing document 4 in one database with
462  // document 4 from another will unnecessarily trigger this, but forcing
463  // the values to be read is fairly harmless, and this is unlikely to be
464  // a common case.
465  doc.internal->need_values();
466  }
467  delete_document(did, value_stats);
468  add_document(did, doc, value_stats);
469 }
470 
471 string
473 {
474  auto i = changes.find(slot);
475  if (i != changes.end()) {
476  auto j = i->second.find(did);
477  if (j != i->second.end()) return j->second;
478  }
479 
480  // Read it from the table.
481  string chunk;
482  Xapian::docid first_did;
483  first_did = get_chunk_containing_did(slot, did, chunk);
484  if (first_did == 0) return string();
485 
486  ValueChunkReader reader(chunk.data(), chunk.size(), first_did);
487  reader.skip_to(did);
488  if (reader.at_end() || reader.get_docid() != did) return string();
489  return reader.get_value();
490 }
491 
492 void
493 GlassValueManager::get_all_values(map<Xapian::valueno, string> & values,
494  Xapian::docid did) const
495 {
496  Assert(values.empty());
497  if (!termlist_table->is_open()) {
498  // Either the database has been closed, or else there's no termlist
499  // table. Check if the postlist table is open to determine which is
500  // the case.
501  if (!postlist_table->is_open())
503  throw Xapian::FeatureUnavailableError("Database has no termlist");
504  }
505  map<Xapian::docid, string>::const_iterator i = slots.find(did);
506  string s;
507  if (i != slots.end()) {
508  s = i->second;
509  } else {
510  // Get from table.
511  if (!termlist_table->get_exact_entry(make_slot_key(did), s)) return;
512  }
513  const char * p = s.data();
514  const char * end = p + s.size();
515  Xapian::valueno prev_slot = static_cast<Xapian::valueno>(-1);
516  while (p != end) {
517  Xapian::valueno slot;
518  if (!unpack_uint(&p, end, &slot)) {
519  throw Xapian::DatabaseCorruptError("Value slot encoding corrupt");
520  }
521  slot += prev_slot + 1;
522  prev_slot = slot;
523  values.insert(make_pair(slot, get_value(did, slot)));
524  }
525 }
526 
527 void
529 {
530  LOGCALL_VOID(DB, "GlassValueManager::get_value_stats", slot);
531  // Invalidate the cache first in case an exception is thrown.
532  mru_slot = Xapian::BAD_VALUENO;
533  get_value_stats(slot, mru_valstats);
534  mru_slot = slot;
535 }
536 
537 void
539 {
540  LOGCALL_VOID(DB, "GlassValueManager::get_value_stats", slot | Literal("[stats]"));
541 
542  string tag;
543  if (postlist_table->get_exact_entry(make_valuestats_key(slot), tag)) {
544  const char * pos = tag.data();
545  const char * end = pos + tag.size();
546 
547  if (!unpack_uint(&pos, end, &(stats.freq))) {
548  if (pos == 0) throw Xapian::DatabaseCorruptError("Incomplete stats item in value table");
549  throw Xapian::RangeError("Frequency statistic in value table is too large");
550  }
551  if (!unpack_string(&pos, end, stats.lower_bound)) {
552  if (pos == 0) throw Xapian::DatabaseCorruptError("Incomplete stats item in value table");
553  throw Xapian::RangeError("Lower bound in value table is too large");
554  }
555  if (stats.lower_bound.empty() && stats.freq != 0) {
556  Assert(false);
557  // This shouldn't happen, but let's code defensively and set the
558  // smallest valid lower bound (a single zero byte) if it somehow
559  // does (it's been reported by a notmuch user).
560  stats.lower_bound.assign(1, '\0');
561  }
562  size_t len = end - pos;
563  if (len == 0) {
564  stats.upper_bound = stats.lower_bound;
565  } else {
566  stats.upper_bound.assign(pos, len);
567  }
568  } else {
569  stats.clear();
570  }
571 }
572 
573 void
574 GlassValueManager::set_value_stats(map<Xapian::valueno, ValueStats> & value_stats)
575 {
576  LOGCALL_VOID(DB, "GlassValueManager::set_value_stats", value_stats);
577  map<Xapian::valueno, ValueStats>::const_iterator i;
578  for (i = value_stats.begin(); i != value_stats.end(); ++i) {
579  string key = make_valuestats_key(i->first);
580  const ValueStats & stats = i->second;
581  if (stats.freq != 0) {
582  string new_value;
583  pack_uint(new_value, stats.freq);
584  pack_string(new_value, stats.lower_bound);
585  // We don't store or count empty values, so neither of the bounds
586  // can be empty. So we can safely store an empty upper bound when
587  // the bounds are equal.
588  if (stats.lower_bound != stats.upper_bound)
589  new_value += stats.upper_bound;
590  postlist_table->add(key, new_value);
591  } else {
592  postlist_table->del(key);
593  }
594  }
595  value_stats.clear();
596  mru_slot = Xapian::BAD_VALUENO;
597 }
#define LOGCALL_STATIC(CATEGORY, TYPE, FUNC, PARAMS)
Definition: debuglog.h:480
#define RETURN(A)
Definition: debuglog.h:482
#define Assert(COND)
Definition: omassert.h:122
Class to hold statistics for a given slot.
Definition: valuestats.h:29
void set_value_stats(std::map< Xapian::valueno, ValueStats > &value_stats)
Write the updated statistics to the table.
static string make_slot_key(Xapian::docid did)
Generate a key for the "used slots" data.
Definition: glass_values.cc:49
#define GLASS_MAX_DOCID
The largest docid value supported by glass.
Definition: glass_defs.h:43
enc
Definition: header.h:52
#define AssertRel(A, REL, B)
Definition: omassert.h:123
Xapian::docid docid_from_key(Xapian::valueno required_slot, const std::string &key)
Definition: glass_values.h:50
Xapian::docid prev_did
ValueIterator values_begin() const
Iterator for the values in this document.
Definition: omdocument.cc:210
Class for iterating over document values.
Definition: valueiterator.h:40
#define LOGCALL_VOID(CATEGORY, FUNC, PARAMS)
Definition: debuglog.h:477
Postlists in glass databases.
STL namespace.
std::string make_valuechunk_key(Xapian::valueno slot, Xapian::docid did)
Generate a key for a value stream chunk.
Definition: glass_values.h:41
std::string upper_bound
An upper bound on the values stored in the given value slot.
Definition: valuestats.h:41
#define rare(COND)
Definition: config.h:543
Xapian::docid get_chunk_containing_did(Xapian::valueno slot, Xapian::docid did, std::string &chunk) const
#define AssertRelParanoid(A, REL, B)
Definition: omassert.h:130
docid get_docid() const
Get the document id which is associated with this document (if any).
Definition: omdocument.cc:220
ValueChunkReader reader
void get_value_stats(Xapian::valueno slot) const
Get the statistics for value slot slot.
Xapian::doccount freq
The number of documents which have a (non-empty) value stored in the slot.
Definition: valuestats.h:33
std::string lower_bound
A lower bound on the values stored in the given value slot.
Definition: valuestats.h:37
const std::string & get_value() const
Definition: glass_values.h:201
Hierarchy of classes which Xapian can throw as exceptions.
Definition: pretty.h:45
RangeError indicates an attempt to access outside the bounds of a container.
Definition: error.h:971
void pack_uint_last(std::string &s, U value)
Append an encoded unsigned integer to a string as the last item.
Definition: pack.h:93
Indicates an attempt to use a feature which is unavailable.
Definition: error.h:719
Xapian::docid new_first_did
void add(const std::string &key, std::string tag, bool already_compressed=false)
Add a key/tag pair to the table, replacing any existing pair with the same key.
void remove_value(Xapian::docid did, Xapian::valueno slot)
ValueUpdater(GlassPostListTable *table_, Xapian::valueno slot_)
Class for iterating over document values.
bool unpack_uint_preserving_sort(const char **p, const char *end, U *result)
Decode an "sort preserved" unsigned integer from a string.
Definition: pack.h:318
static void throw_database_closed()
Throw an exception indicating that the database is closed.
static const size_t CHUNK_SIZE_THRESHOLD
Xapian::docid last_allowed_did
ValueIterator values_end() const
Equivalent end iterator for values_begin().
Definition: document.h:271
Xapian::Internal::intrusive_ptr< Internal > internal
Definition: document.h:63
void update(Xapian::docid did, const string &value)
void assign(const char *p_, size_t len, Xapian::docid did_)
Definition: glass_values.cc:72
DatabaseCorruptError indicates database corruption was detected.
Definition: error.h:409
void add_value(Xapian::docid did, Xapian::valueno slot, const std::string &val)
void skip_to(Xapian::docid target)
Definition: glass_values.cc:98
void pack_uint(std::string &s, U value)
Append an encoded unsigned integer to a string.
Definition: pack.h:382
void pack_string(std::string &s, const std::string &value)
Append an encoded std::string to a string.
Definition: pack.h:477
Interface to Btree cursors.
void clear()
Clear the statistics.
Definition: valuestats.h:48
GlassValueManager class.
Xapian::valueno slot
Xapian::docid get_docid() const
Definition: glass_values.h:199
GlassCursor * cursor_get() const
Get a cursor for reading from the table.
Pack types into strings and unpack them again.
unsigned valueno
The number for a value slot in a document.
Definition: types.h:108
bool unpack_uint(const char **p, const char *end, U *result)
Decode an unsigned integer from a string.
Definition: pack.h:413
Xapian::docid first_did
void delete_document(Xapian::docid did, std::map< Xapian::valueno, ValueStats > &value_stats)
bool unpack_string(const char **p, const char *end, std::string &result)
Decode a std::string from a string.
Definition: pack.h:504
A TermList in a glass database.
unsigned XAPIAN_DOCID_BASE_TYPE docid
A unique identifier for a document.
Definition: types.h:52
const valueno BAD_VALUENO
Reserved value to indicate "no valueno".
Definition: types.h:125
GlassPostListTable * table
void append_to_stream(Xapian::docid did, const string &value)
void add_document(Xapian::docid did, const Xapian::Document &doc, std::map< Xapian::valueno, ValueStats > &value_stats)
A handle representing a document in a Xapian database.
Definition: document.h:61
bool del(const std::string &key)
Delete an entry from the table.
static string make_valuestats_key(Xapian::valueno slot)
Generate a key for a value statistics item.
Definition: glass_values.cc:63
Wrapper around standard unique_ptr template.
void replace_document(Xapian::docid did, const Xapian::Document &doc, std::map< Xapian::valueno, ValueStats > &value_stats)
Debug logging macros.
#define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS)
Definition: debuglog.h:476
Xapian::valueno get_valueno() const
Return the value slot number for the current position.
std::string get_value(Xapian::docid did, Xapian::valueno slot) const
void pack_uint_preserving_sort(std::string &s, U value)
Append an encoded unsigned integer to a string, preserving the sort order.
Definition: pack.h:269
void get_all_values(std::map< Xapian::valueno, std::string > &values, Xapian::docid did) const