xapian-core  2.1.0
glass_synonym.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2004,2005,2006,2007,2008,2009,2011,2017,2024,2026 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, see
18  * <https://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 #include "glass_synonym.h"
23 
24 #include "xapian/error.h"
25 
26 #include "glass_cursor.h"
27 #include "glass_database.h"
28 #include "clamp_cast.h"
29 #include "debuglog.h"
30 #include "stringutils.h"
31 #include "api/vectortermlist.h"
32 
33 #include <set>
34 #include <string>
35 #include <string_view>
36 #include <vector>
37 
38 using namespace std;
39 
40 // We XOR the length values with this so that they are more likely to coincide
41 // with lower case ASCII letters, which are likely to be common. This means
42 // that zlib should do a better job of compressing tag values.
43 #define MAGIC_XOR_VALUE 96
44 
45 void
47 {
48  if (last_term.empty()) return;
49 
50  if (last_synonyms.empty()) {
51  del(last_term);
52  } else {
53  string tag;
54  for (const auto& synonym : last_synonyms) {
55  tag += uint8_t(synonym.size() ^ MAGIC_XOR_VALUE);
56  tag += synonym;
57  }
58  add(last_term, tag);
59  last_synonyms.clear();
60  }
61  last_term.resize(0);
62 }
63 
64 void
65 GlassSynonymTable::add_synonym(string_view term, string_view synonym)
66 {
67  if (last_term != term) {
68  merge_changes();
69  last_term = term;
70 
71  string tag;
72  if (get_exact_entry(term, tag)) {
73  const char * p = tag.data();
74  const char * end = p + tag.size();
75  while (p != end) {
76  size_t len;
77  if (p == end ||
78  (len = uint8_t(*p) ^ MAGIC_XOR_VALUE) >= size_t(end - p))
79  throw Xapian::DatabaseCorruptError("Bad synonym data");
80  ++p;
81  last_synonyms.insert(string(p, len));
82  p += len;
83  }
84  }
85  }
86 
87  last_synonyms.emplace(synonym);
88 }
89 
90 void
91 GlassSynonymTable::remove_synonym(string_view term, string_view synonym)
92 {
93  if (last_term != term) {
94  merge_changes();
95  last_term = term;
96 
97  string tag;
98  if (get_exact_entry(term, tag)) {
99  const char * p = tag.data();
100  const char * end = p + tag.size();
101  while (p != end) {
102  size_t len;
103  if (p == end ||
104  (len = uint8_t(*p) ^ MAGIC_XOR_VALUE) >= size_t(end - p))
105  throw Xapian::DatabaseCorruptError("Bad synonym data");
106  ++p;
107  last_synonyms.emplace(p, len);
108  p += len;
109  }
110  }
111  }
112 
113 #ifdef __cpp_lib_associative_heterogeneous_erasure // C++23
114  last_synonyms.erase(synonym);
115 #else
116  last_synonyms.erase(string(synonym));
117 #endif
118 }
119 
120 void
122 {
123  // We don't actually ever need to merge_changes() here, but it's quite
124  // likely that someone might clear_synonyms() and then add_synonym() for
125  // the same term. The alternative we could otherwise optimise for (modify
126  // synonyms for a term, then clear those for another, then modify those for
127  // the first term again) seems much less likely.
128  if (last_term == term) {
129  last_synonyms.clear();
130  } else {
131  merge_changes();
132  last_term = term;
133  }
134 }
135 
136 TermList*
138 {
139  vector<string> synonyms;
140 
141  if (last_term == term) {
142  if (last_synonyms.empty()) return NULL;
143 
144  synonyms.reserve(last_synonyms.size());
145  for (const auto& i : last_synonyms) {
146  synonyms.push_back(i);
147  }
148  } else {
149  string tag;
150  if (!get_exact_entry(term, tag)) return NULL;
151 
152  const char * p = tag.data();
153  const char * end = p + tag.size();
154  while (p != end) {
155  size_t len;
156  if (p == end ||
157  (len = uint8_t(*p) ^ MAGIC_XOR_VALUE) >= size_t(end - p))
158  throw Xapian::DatabaseCorruptError("Bad synonym data");
159  ++p;
160  synonyms.push_back(string(p, len));
161  p += len;
162  }
163  }
164 
165  return new VectorTermList(synonyms.begin(), synonyms.end());
166 }
167 
169 
171 {
172  LOGCALL_DTOR(DB, "GlassSynonymTermList");
173  delete cursor;
174 }
175 
178 {
179  // This is an over-estimate, but we only use this value to build a balanced
180  // or-tree, and it'll do a decent enough job for that.
181  auto entry_count = database->synonym_table.get_entry_count();
182  return clamp_cast<Xapian::termcount>(entry_count);
183 }
184 
187 {
188  throw Xapian::InvalidOperationError("GlassSynonymTermList::get_termfreq() not meaningful");
189 }
190 
191 TermList *
193 {
194  LOGCALL(DB, TermList *, "GlassSynonymTermList::next", NO_ARGS);
195  Assert(!cursor->after_end());
196 
197  if (!cursor->next() || !startswith(cursor->current_key, prefix)) {
198  // We've reached the end of the prefixed terms.
199  RETURN(this);
200  }
201  current_term = cursor->current_key;
202 
203  RETURN(NULL);
204 }
205 
206 TermList*
208 {
209  LOGCALL(DB, TermList*, "GlassSynonymTermList::skip_to", term);
210  Assert(!cursor->after_end());
211 
212  if (cursor->find_entry_ge(term)) {
213  // Exact match.
214  current_term = term;
215  } else {
216  // The exact term we asked for isn't there, so check if the next
217  // term after it also has the right prefix.
218  if (cursor->after_end() || !startswith(cursor->current_key, prefix)) {
219  // We've reached the end of the prefixed terms.
220  RETURN(this);
221  }
222  current_term = cursor->current_key;
223  }
224  RETURN(NULL);
225 }
Cast a value to a type, clamping out of range values.
void clear_synonyms(std::string_view term)
Remove all synonyms for term.
void add_synonym(std::string_view term, std::string_view synonym)
Add a synonym for term.
TermList * open_termlist(std::string_view term)
Open synonym termlist for a term.
void remove_synonym(std::string_view term, std::string_view synonym)
Remove a synonym for term.
Xapian::doccount get_termfreq() const
Return the term frequency for the term at the current position.
Xapian::termcount get_approx_size() const
Return approximate size of this termlist.
~GlassSynonymTermList()
Destructor.
TermList * next()
Advance to the next term in the list.
TermList * skip_to(std::string_view term)
Advance to the first term which is >= term.
This class stores a list of terms.
DatabaseCorruptError indicates database corruption was detected.
Definition: error.h:397
InvalidOperationError indicates the API was used in an invalid way.
Definition: error.h:271
Abstract base class for termlists.
Definition: termlist.h:42
string term
PositionList * p
Debug logging macros.
#define RETURN(...)
Definition: debuglog.h:484
#define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS)
Definition: debuglog.h:478
#define LOGCALL_DTOR(CATEGORY, CLASS)
Definition: debuglog.h:481
Hierarchy of classes which Xapian can throw as exceptions.
Interface to Btree cursors.
C++ class definition for glass database.
#define MAGIC_XOR_VALUE
Synonym data for a glass database.
unsigned XAPIAN_TERMCOUNT_BASE_TYPE termcount
A counts of terms.
Definition: types.h:64
unsigned XAPIAN_DOCID_BASE_TYPE doccount
A count of documents.
Definition: types.h:37
#define Assert(COND)
Definition: omassert.h:122
Various handy string-related helpers.
bool startswith(std::string_view s, char pfx)
Definition: stringutils.h:56
A vector-like container of terms which can be iterated.