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