xapian-core  2.1.0
honey_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 "honey_synonym.h"
23 
24 #include "xapian/error.h"
25 
26 #include "honey_cursor.h"
27 #include "honey_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 
55  for (auto&& synonym : last_synonyms) {
56  tag += uint8_t(synonym.size() ^ MAGIC_XOR_VALUE);
57  tag += synonym;
58  }
59 
60  add(last_term, tag);
61  last_synonyms.clear();
62  }
63  last_term.resize(0);
64 }
65 
66 void
67 HoneySynonymTable::add_synonym(string_view term, string_view synonym)
68 {
69  if (last_term != term) {
70  merge_changes();
71  last_term = term;
72 
73  string tag;
74  if (get_exact_entry(term, tag)) {
75  const char* p = tag.data();
76  const char* end = p + tag.size();
77  while (p != end) {
78  size_t len;
79  if (p == end ||
80  (len = uint8_t(*p) ^ MAGIC_XOR_VALUE) >= size_t(end - p))
81  throw Xapian::DatabaseCorruptError("Bad synonym data");
82  ++p;
83  last_synonyms.insert(string(p, len));
84  p += len;
85  }
86  }
87  }
88 
89  last_synonyms.emplace(synonym);
90 }
91 
92 void
93 HoneySynonymTable::remove_synonym(string_view term, string_view synonym)
94 {
95  if (last_term != term) {
96  merge_changes();
97  last_term = term;
98 
99  string tag;
100  if (get_exact_entry(term, tag)) {
101  const char* p = tag.data();
102  const char* end = p + tag.size();
103  while (p != end) {
104  size_t len;
105  if (p == end ||
106  (len = uint8_t(*p) ^ MAGIC_XOR_VALUE) >= size_t(end - p))
107  throw Xapian::DatabaseCorruptError("Bad synonym data");
108  ++p;
109  last_synonyms.emplace(p, len);
110  p += len;
111  }
112  }
113  }
114 
115 #ifdef __cpp_lib_associative_heterogeneous_erasure // C++23
116  last_synonyms.erase(synonym);
117 #else
118  last_synonyms.erase(string(synonym));
119 #endif
120 }
121 
122 void
124 {
125  // We don't actually ever need to merge_changes() here, but it's quite
126  // likely that someone might clear_synonyms() and then add_synonym() for
127  // the same term. The alternative we could otherwise optimise for (modify
128  // synonyms for a term, then clear those for another, then modify those for
129  // the first term again) seems much less likely.
130  if (last_term == term) {
131  last_synonyms.clear();
132  } else {
133  merge_changes();
134  last_term = term;
135  }
136 }
137 
138 TermList*
140 {
141  vector<string> synonyms;
142 
143  if (last_term == term) {
144  if (last_synonyms.empty()) return NULL;
145 
146  synonyms.reserve(last_synonyms.size());
147  for (auto&& i : last_synonyms) {
148  synonyms.push_back(i);
149  }
150  } else {
151  string tag;
152  if (!get_exact_entry(term, tag)) return NULL;
153 
154  const char* p = tag.data();
155  const char* end = p + tag.size();
156  while (p != end) {
157  size_t len;
158  if (p == end ||
159  (len = uint8_t(*p) ^ MAGIC_XOR_VALUE) >= size_t(end - p))
160  throw Xapian::DatabaseCorruptError("Bad synonym data");
161  ++p;
162  synonyms.push_back(string(p, len));
163  p += len;
164  }
165  }
166 
167  return new VectorTermList(synonyms.begin(), synonyms.end());
168 }
169 
171 
173 {
174  LOGCALL_DTOR(DB, "HoneySynonymTermList");
175  delete cursor;
176 }
177 
180 {
181  // This is an over-estimate, but we only use this value to build a balanced
182  // or-tree, and it'll do a decent enough job for that.
183  auto entry_count = database->synonym_table.get_approx_entry_count();
184  return clamp_cast<Xapian::termcount>(entry_count);
185 }
186 
189 {
190  throw Xapian::InvalidOperationError("HoneySynonymTermList::get_termfreq() "
191  "not meaningful");
192 }
193 
194 TermList*
196 {
197  LOGCALL(DB, TermList*, "HoneySynonymTermList::next", NO_ARGS);
198  if (cursor->after_end()) {
199  // This is the first action on a new HoneySynonymTermList.
200  if (cursor->find_entry_ge(prefix))
201  RETURN(NULL);
202  } else {
203  cursor->next();
204  }
205  if (cursor->after_end() || !startswith(cursor->current_key, prefix)) {
206  // We've reached the end of the prefixed terms.
207  RETURN(this);
208  }
209  current_term = cursor->current_key;
210 
211  RETURN(NULL);
212 }
213 
214 TermList*
216 {
217  LOGCALL(DB, TermList*, "HoneySynonymTermList::skip_to", term);
218  if (cursor->after_end() && prefix > term) {
219  // This is the first action on a new HoneySynonymTermList and we were
220  // asked to skip to a term before the prefix - this ought to leave us
221  // on the first term with the specified prefix.
222  RETURN(skip_to(prefix));
223  }
224 
225  if (cursor->find_entry_ge(term)) {
226  // Exact match.
227  current_term = term;
228  } else {
229  // The exact term we asked for isn't there, so check if the next
230  // term after it also has the right prefix.
231  if (cursor->after_end() || !startswith(cursor->current_key, prefix)) {
232  // We've reached the end of the prefixed terms.
233  RETURN(this);
234  }
235  current_term = cursor->current_key;
236  }
237  RETURN(NULL);
238 }
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.
void remove_synonym(std::string_view term, std::string_view synonym)
Remove a synonym for term.
TermList * open_termlist(std::string_view term) const
Open synonym termlist for a term.
TermList * skip_to(std::string_view term)
Advance to the first term which is >= term.
Xapian::doccount get_termfreq() const
Return the term frequency for the term at the current position.
~HoneySynonymTermList()
Destructor.
Xapian::termcount get_approx_size() const
Return approximate size of this termlist.
TermList * next()
Advance to the next term in the list.
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.
HoneyCursor class.
Database using honey backend.
#define MAGIC_XOR_VALUE
Synonym data for a honey 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
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.