Branch data Line data Source code
1 : : /* omdatabase.cc: External interface for running queries
2 : : *
3 : : * Copyright 1999,2000,2001 BrightStation PLC
4 : : * Copyright 2001,2002 Ananova Ltd
5 : : * Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2010 Olly Betts
6 : : * Copyright 2006,2008 Lemur Consulting Ltd
7 : : *
8 : : * This program is free software; you can redistribute it and/or
9 : : * modify it under the terms of the GNU General Public License as
10 : : * published by the Free Software Foundation; either version 2 of the
11 : : * License, or (at your option) any later version.
12 : : *
13 : : * This program is distributed in the hope that it will be useful,
14 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : : * GNU General Public License for more details.
17 : : *
18 : : * You should have received a copy of the GNU General Public License
19 : : * along with this program; if not, write to the Free Software
20 : : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 : : * USA
22 : : */
23 : :
24 : : #include <config.h>
25 : :
26 : : #include "autoptr.h"
27 : :
28 : : #include <xapian/error.h>
29 : : #include <xapian/positioniterator.h>
30 : : #include <xapian/postingiterator.h>
31 : : #include <xapian/termiterator.h>
32 : : #include <xapian/unicode.h>
33 : :
34 : : #include "omassert.h"
35 : : #include "debuglog.h"
36 : : #include "../backends/multi/multi_postlist.h"
37 : : #include "../backends/multi/multi_termlist.h"
38 : : #include "alltermslist.h"
39 : : #include "multialltermslist.h"
40 : : #include "multivaluelist.h"
41 : : #include "database.h"
42 : : #include "editdistance.h"
43 : : #include "ortermlist.h"
44 : : #include "noreturn.h"
45 : :
46 : : #include <cstdlib> // For abs().
47 : :
48 : : #include <cstring>
49 : : #include <vector>
50 : :
51 : : using namespace std;
52 : :
53 : : XAPIAN_NORETURN(static void docid_zero_invalid());
54 : 36 : static void docid_zero_invalid()
55 : : {
56 : 36 : throw Xapian::InvalidArgumentError("Document ID 0 is invalid");
57 : : }
58 : :
59 : : XAPIAN_NORETURN(static void no_subdatabases());
60 : 4 : static void no_subdatabases()
61 : : {
62 : 4 : throw Xapian::DocNotFoundError("No subdatabases");
63 : : }
64 : :
65 : : namespace Xapian {
66 : :
67 : 201550 : Database::Database()
68 : : {
69 : : LOGCALL_CTOR(API, "Database", NO_ARGS);
70 : 201550 : }
71 : :
72 : 13275 : Database::Database(Database::Internal *internal_)
73 : : {
74 : : LOGCALL_CTOR(API, "Database", internal_);
75 : 13275 : Xapian::Internal::RefCntPtr<Database::Internal> newi(internal_);
76 : 13275 : internal.push_back(newi);
77 : 13275 : }
78 : :
79 : 389934 : Database::Database(const Database &other)
80 : : {
81 : : LOGCALL_CTOR(API, "Database", other);
82 : 389934 : internal = other.internal;
83 : 389934 : }
84 : :
85 : : void
86 : 189350 : Database::operator=(const Database &other)
87 : : {
88 : : LOGCALL_VOID(API, "Database::operator=", other);
89 [ + + ]: 189350 : if (this == &other) {
90 : : LOGLINE(API, "Database assigned to itself");
91 : 20 : return;
92 : : }
93 : :
94 : 189350 : internal = other.internal;
95 : : }
96 : :
97 : 609031 : Database::~Database()
98 : : {
99 : : LOGCALL_DTOR(API, "Database");
100 [ + - ][ - + ]: 609031 : }
[ - + ]
101 : :
102 : : void
103 : 2580 : Database::reopen()
104 : : {
105 : : LOGCALL_VOID(API, "Database::reopen", NO_ARGS);
106 : 2580 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::iterator i;
107 [ + + ]: 5143 : for (i = internal.begin(); i != internal.end(); ++i) {
108 : 2589 : (*i)->reopen();
109 : : }
110 : 2554 : }
111 : :
112 : : void
113 : 61 : Database::close()
114 : : {
115 : : LOGCALL_VOID(API, "Database::close", NO_ARGS);
116 : 61 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::iterator i;
117 [ + + ]: 131 : for (i = internal.begin(); i != internal.end(); ++i) {
118 : 70 : (*i)->close();
119 : : }
120 : 61 : }
121 : :
122 : : void
123 : 1416 : Database::add_database(const Database & database)
124 : : {
125 : : LOGCALL_VOID(API, "Database::add_database", database);
126 [ - + ]: 1416 : if (this == &database) {
127 : : LOGLINE(API, "Database added to itself");
128 : 0 : throw Xapian::InvalidArgumentError("Can't add a Database to itself");
129 : : }
130 : 1416 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
131 [ + + ]: 2871 : for (i = database.internal.begin(); i != database.internal.end(); ++i) {
132 : 1455 : internal.push_back(*i);
133 : : }
134 : 1416 : }
135 : :
136 : : PostingIterator
137 : 5276 : Database::postlist_begin(const string &tname) const
138 : : {
139 : : LOGCALL(API, PostingIterator, "Database::postlist_begin", tname);
140 : :
141 : : // Don't bother checking that the term exists first. If it does, we
142 : : // just end up doing more work, and if it doesn't, we save very little
143 : : // work.
144 : :
145 : : // Handle the common case of a single database specially.
146 [ + + ]: 5276 : if (internal.size() == 1)
147 : 5181 : RETURN(PostingIterator(internal[0]->open_post_list(tname)));
148 : :
149 [ + + ]: 95 : if (rare(internal.size() == 0))
150 : 2 : RETURN(PostingIterator(NULL));
151 : :
152 : 93 : vector<LeafPostList *> pls;
153 : : try {
154 : 93 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
155 [ + + ]: 267 : for (i = internal.begin(); i != internal.end(); ++i) {
156 : 180 : pls.push_back((*i)->open_post_list(tname));
157 : 174 : pls.back()->next();
158 : : }
159 : : Assert(pls.begin() != pls.end());
160 : 12 : } catch (...) {
161 : 6 : vector<LeafPostList *>::iterator i;
162 [ - + ]: 6 : for (i = pls.begin(); i != pls.end(); ++i) {
163 [ # # ]: 0 : delete *i;
164 : 0 : *i = 0;
165 : : }
166 : 6 : throw;
167 : : }
168 : :
169 : 5253 : RETURN(PostingIterator(new MultiPostList(pls, *this)));
170 : : }
171 : :
172 : : TermIterator
173 : 63357 : Database::termlist_begin(Xapian::docid did) const
174 : : {
175 : : LOGCALL(API, TermIterator, "Database::termlist_begin", did);
176 [ + + ]: 63357 : if (did == 0)
177 : 13 : docid_zero_invalid();
178 : :
179 : 63344 : unsigned int multiplier = internal.size();
180 [ + + ]: 63344 : if (rare(multiplier == 0))
181 : 1 : no_subdatabases();
182 : : TermList *tl;
183 [ + + ]: 63343 : if (multiplier == 1) {
184 : : // There's no need for the MultiTermList wrapper in the common case
185 : : // where we're only dealing with a single database.
186 : 63292 : tl = internal[0]->open_term_list(did);
187 : : } else {
188 : : Assert(multiplier != 0);
189 : 51 : Xapian::doccount n = (did - 1) % multiplier; // which actual database
190 : 51 : Xapian::docid m = (did - 1) / multiplier + 1; // real docid in that database
191 : :
192 : 51 : tl = new MultiTermList(internal[n]->open_term_list(m), *this, n);
193 : : }
194 : 33244 : RETURN(TermIterator(tl));
195 : : }
196 : :
197 : : TermIterator
198 : 425 : Database::allterms_begin() const
199 : : {
200 : 425 : return allterms_begin("");
201 : : }
202 : :
203 : : TermIterator
204 : 963 : Database::allterms_begin(const std::string & prefix) const
205 : : {
206 : : LOGCALL(API, TermIterator, "Database::allterms_begin", NO_ARGS);
207 : : TermList * tl;
208 [ + + ]: 963 : if (rare(internal.size() == 0)) {
209 : 2 : tl = NULL;
210 [ + + ]: 961 : } else if (internal.size() == 1) {
211 : 856 : tl = internal[0]->open_allterms(prefix);
212 : : } else {
213 : 105 : tl = new MultiAllTermsList(internal, prefix);
214 : : }
215 : 957 : RETURN(TermIterator(tl));
216 : : }
217 : :
218 : : bool
219 : 3787 : Database::has_positions() const
220 : : {
221 : : LOGCALL(API, bool, "Database::has_positions", NO_ARGS);
222 : : // If any sub-database has positions, the combined database does.
223 : 3787 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
224 [ + + ]: 5069 : for (i = internal.begin(); i != internal.end(); ++i) {
225 [ + + ]: 3787 : if ((*i)->has_positions()) RETURN(true);
226 : : }
227 : 3780 : RETURN(false);
228 : : }
229 : :
230 : : PositionIterator
231 : 2417 : Database::positionlist_begin(Xapian::docid did, const string &tname) const
232 : : {
233 : : LOGCALL(API, PositionIterator, "Database::positionlist_begin", did | tname);
234 [ + + ]: 2417 : if (tname.empty())
235 : 109 : throw InvalidArgumentError("Zero length terms are invalid");
236 [ - + ]: 2308 : if (did == 0)
237 : 0 : docid_zero_invalid();
238 : :
239 : 2308 : unsigned int multiplier = internal.size();
240 [ + + ]: 2308 : if (rare(multiplier == 0))
241 : 1 : no_subdatabases();
242 : 2307 : Xapian::doccount n = (did - 1) % multiplier; // which actual database
243 : 2307 : Xapian::docid m = (did - 1) / multiplier + 1; // real docid in that database
244 : 2307 : RETURN(PositionIterator(internal[n]->open_position_list(m, tname)));
245 : : }
246 : :
247 : : Xapian::doccount
248 : 186952 : Database::get_doccount() const
249 : : {
250 : : LOGCALL(API, Xapian::doccount, "Database::get_doccount", NO_ARGS);
251 : 186952 : Xapian::doccount docs = 0;
252 : 186952 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
253 [ + + ]: 445728 : for (i = internal.begin(); i != internal.end(); ++i) {
254 : 258783 : docs += (*i)->get_doccount();
255 : : }
256 : 186945 : RETURN(docs);
257 : : }
258 : :
259 : : Xapian::docid
260 : 15377 : Database::get_lastdocid() const
261 : : {
262 : : LOGCALL(API, Xapian::docid, "Database::get_lastdocid", NO_ARGS);
263 : 15377 : Xapian::docid did = 0;
264 : :
265 : 15377 : unsigned int multiplier = internal.size();
266 [ + + ]: 31278 : for (Xapian::doccount i = 0; i < multiplier; ++i) {
267 : 15901 : Xapian::docid did_i = internal[i]->get_lastdocid();
268 [ + + ]: 15901 : if (did_i) did = std::max(did, (did_i - 1) * multiplier + i + 1);
269 : : }
270 : 15377 : RETURN(did);
271 : : }
272 : :
273 : : Xapian::doclength
274 : 4340 : Database::get_avlength() const
275 : : {
276 : : LOGCALL(API, Xapian::doclength, "Database::get_avlength", NO_ARGS);
277 : 4340 : Xapian::doccount docs = 0;
278 : 4340 : Xapian::doclength totlen = 0;
279 : :
280 : 4340 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
281 [ + + ]: 8754 : for (i = internal.begin(); i != internal.end(); ++i) {
282 : 4414 : Xapian::doccount db_doccount = (*i)->get_doccount();
283 : 4414 : docs += db_doccount;
284 : 4414 : totlen += (*i)->get_avlength() * db_doccount;
285 : : }
286 : : LOGLINE(UNKNOWN, "get_avlength() = " << totlen << " / " << docs <<
287 : : " (from " << internal.size() << " dbs)");
288 : :
289 [ + + ]: 4340 : if (docs == 0) RETURN(0.0);
290 : 4340 : RETURN(totlen / docs);
291 : : }
292 : :
293 : : Xapian::doccount
294 : 74284 : Database::get_termfreq(const string & tname) const
295 : : {
296 : : LOGCALL(API, Xapian::doccount, "Database::get_termfreq", tname);
297 [ + + ]: 74284 : if (tname.empty()) RETURN(get_doccount());
298 : :
299 : 73946 : Xapian::doccount tf = 0;
300 : 73946 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
301 [ + + ]: 148518 : for (i = internal.begin(); i != internal.end(); i++) {
302 : 74572 : tf += (*i)->get_termfreq(tname);
303 : : }
304 : 74284 : RETURN(tf);
305 : : }
306 : :
307 : : Xapian::termcount
308 : 4330 : Database::get_collection_freq(const string & tname) const
309 : : {
310 : : LOGCALL(API, Xapian::termcount, "Database::get_collection_freq", tname);
311 [ + + ]: 4330 : if (tname.empty()) RETURN(get_doccount());
312 : :
313 : 3992 : Xapian::termcount cf = 0;
314 : 3992 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
315 [ + + ]: 8084 : for (i = internal.begin(); i != internal.end(); i++) {
316 : 4092 : cf += (*i)->get_collection_freq(tname);
317 : : }
318 : 4330 : RETURN(cf);
319 : : }
320 : :
321 : : Xapian::doccount
322 : 935 : Database::get_value_freq(Xapian::valueno valno) const
323 : : {
324 : : LOGCALL(API, Xapian::doccount, "Database::get_value_freq", valno);
325 : :
326 : 935 : Xapian::doccount vf = 0;
327 : 935 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
328 [ + + ]: 1747 : for (i = internal.begin(); i != internal.end(); i++) {
329 : 959 : vf += (*i)->get_value_freq(valno);
330 : : }
331 : 788 : RETURN(vf);
332 : : }
333 : :
334 : : string
335 : 678 : Database::get_value_lower_bound(Xapian::valueno valno) const
336 : : {
337 : : LOGCALL(API, string, "Database::get_value_lower_bound", valno);
338 : :
339 [ - + ]: 678 : if (rare(internal.empty())) RETURN(string());
340 : :
341 : 678 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
342 : 678 : i = internal.begin();
343 : 678 : string full_lb = (*i)->get_value_lower_bound(valno);
344 [ + + ]: 700 : while (++i != internal.end()) {
345 : 22 : string lb = (*i)->get_value_lower_bound(valno);
346 [ + + ]: 22 : if (lb < full_lb) full_lb = lb;
347 : : }
348 : 678 : RETURN(full_lb);
349 : : }
350 : :
351 : : std::string
352 : 1038 : Database::get_value_upper_bound(Xapian::valueno valno) const
353 : : {
354 : : LOGCALL(API, std::string, "Database::get_value_upper_bound", valno);
355 : :
356 : 1038 : std::string full_ub;
357 : 1038 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
358 [ + + ]: 1947 : for (i = internal.begin(); i != internal.end(); i++) {
359 : 1062 : std::string ub = (*i)->get_value_upper_bound(valno);
360 [ + + ]: 909 : if (ub > full_ub)
361 : 692 : full_ub = ub;
362 : : }
363 : 153 : RETURN(full_ub);
364 : : }
365 : :
366 : : Xapian::termcount
367 : 722101 : Database::get_doclength_lower_bound() const
368 : : {
369 : : LOGCALL(API, Xapian::termcount, "Database::get_doclength_lower_bound", NO_ARGS);
370 : :
371 [ - + ]: 722101 : if (rare(internal.empty())) RETURN(0);
372 : :
373 : 722101 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
374 : 722101 : i = internal.begin();
375 : 722101 : Xapian::termcount full_lb = (*i)->get_doclength_lower_bound();
376 [ + + ]: 1144716 : while (++i != internal.end()) {
377 : 422615 : Xapian::termcount lb = (*i)->get_doclength_lower_bound();
378 [ + + ]: 422615 : if (lb < full_lb) full_lb = lb;
379 : : }
380 : 722101 : RETURN(full_lb);
381 : : }
382 : :
383 : : Xapian::termcount
384 : 4614 : Database::get_doclength_upper_bound() const
385 : : {
386 : : LOGCALL(API, Xapian::termcount, "Database::get_doclength_upper_bound", NO_ARGS);
387 : :
388 : 4614 : Xapian::termcount full_ub = 0;
389 : 4614 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
390 [ + + ]: 9468 : for (i = internal.begin(); i != internal.end(); i++) {
391 : 4854 : Xapian::termcount ub = (*i)->get_doclength_upper_bound();
392 [ + + ]: 4854 : if (ub > full_ub) full_ub = ub;
393 : : }
394 : 4614 : RETURN(full_ub);
395 : : }
396 : :
397 : : Xapian::termcount
398 : 474438 : Database::get_wdf_upper_bound(const string & term) const
399 : : {
400 : : LOGCALL(API, Xapian::termcount, "Database::get_wdf_upper_bound", term);
401 : :
402 : 474438 : Xapian::termcount full_ub = 0;
403 : 474438 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
404 [ + + ]: 1229097 : for (i = internal.begin(); i != internal.end(); i++) {
405 : 754659 : Xapian::termcount ub = (*i)->get_wdf_upper_bound(term);
406 [ + + ]: 754659 : if (ub > full_ub) full_ub = ub;
407 : : }
408 : 474438 : RETURN(full_ub);
409 : : }
410 : :
411 : : ValueIterator
412 : 3644 : Database::valuestream_begin(Xapian::valueno slot) const
413 : : {
414 : : LOGCALL(API, ValueIterator, "Database::valuestream_begin", slot);
415 [ + + ]: 3644 : if (internal.size() == 0)
416 : 1 : RETURN(ValueIterator());
417 [ + + ]: 3643 : if (internal.size() != 1)
418 : 678 : RETURN(ValueIterator(new MultiValueList(internal, slot)));
419 : 3644 : RETURN(ValueIterator(internal[0]->open_value_list(slot)));
420 : : }
421 : :
422 : : Xapian::termcount
423 : 99792 : Database::get_doclength(Xapian::docid did) const
424 : : {
425 : : LOGCALL(API, Xapian::termcount, "Database::get_doclength", did);
426 [ - + ]: 99792 : if (did == 0)
427 : 0 : docid_zero_invalid();
428 : :
429 : 99792 : unsigned int multiplier = internal.size();
430 [ + + ]: 99792 : if (rare(multiplier == 0))
431 : 1 : no_subdatabases();
432 : 99791 : Xapian::doccount n = (did - 1) % multiplier; // which actual database
433 : 99791 : Xapian::docid m = (did - 1) / multiplier + 1; // real docid in that database
434 : 99791 : RETURN(internal[n]->get_doclength(m));
435 : : }
436 : :
437 : : Document
438 : 1012602 : Database::get_document(Xapian::docid did) const
439 : : {
440 : : LOGCALL(API, Document, "Database::get_document", did);
441 [ + + ]: 1012602 : if (did == 0)
442 : 13 : docid_zero_invalid();
443 : :
444 : 1012589 : unsigned int multiplier = internal.size();
445 [ + + ]: 1012589 : if (rare(multiplier == 0))
446 : 1 : no_subdatabases();
447 : 1012588 : Xapian::doccount n = (did - 1) % multiplier; // which actual database
448 : 1012588 : Xapian::docid m = (did - 1) / multiplier + 1; // real docid in that database
449 : :
450 : : // Open non-lazily so we throw DocNotFoundError if the doc doesn't exist.
451 : 1012588 : RETURN(Document(internal[n]->open_document(m, false)));
452 : : }
453 : :
454 : : Document::Internal *
455 : 522952 : Database::get_document_lazily(Xapian::docid did) const
456 : : {
457 : : LOGCALL(DB, Document::Internal *, "Database::get_document_lazily", did);
458 [ - + ]: 522952 : if (did == 0)
459 : 0 : docid_zero_invalid();
460 : :
461 : 522952 : unsigned int multiplier = internal.size();
462 : : Assert(multiplier != 0);
463 : 522952 : Xapian::doccount n = (did - 1) % multiplier; // which actual database
464 : 522952 : Xapian::docid m = (did - 1) / multiplier + 1; // real docid in that database
465 : :
466 : 522952 : RETURN(internal[n]->open_document(m, true));
467 : : }
468 : :
469 : : bool
470 : 3176 : Database::term_exists(const string & tname) const
471 : : {
472 : : LOGCALL(API, bool, "Database::term_exists", tname);
473 [ + + ]: 3176 : if (tname.empty()) {
474 : 109 : RETURN(get_doccount() != 0);
475 : : }
476 : 3067 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
477 [ + + ]: 3205 : for (i = internal.begin(); i != internal.end(); ++i) {
478 [ + + ]: 2999 : if ((*i)->term_exists(tname)) RETURN(true);
479 : : }
480 : 3176 : RETURN(false);
481 : : }
482 : :
483 : : void
484 : 120 : Database::keep_alive()
485 : : {
486 : : LOGCALL_VOID(API, "Database::keep_alive", NO_ARGS);
487 : 120 : vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
488 [ + + ]: 240 : for (i = internal.begin(); i != internal.end(); ++i) {
489 : 120 : (*i)->keep_alive();
490 : : }
491 : 120 : }
492 : :
493 : : string
494 : 2 : Database::get_description() const
495 : : {
496 : : /// \todo display contents of the database
497 : 2 : return "Database()";
498 : : }
499 : :
500 : : // We sum the character frequency histogram absolute differences to compute a
501 : : // lower bound on the edit distance. Rather than counting each Unicode code
502 : : // point uniquely, we use an array with VEC_SIZE elements and tally code points
503 : : // modulo VEC_SIZE which can only reduce the bound we calculate.
504 : : //
505 : : // There will be a trade-off between how good the bound is and how large and
506 : : // array is used (a larger array takes more time to clear and sum over). The
507 : : // value 64 is somewhat arbitrary - it works as well as 128 for the testsuite
508 : : // but that may not reflect real world performance. FIXME: profile and tune.
509 : :
510 : : #define VEC_SIZE 64
511 : :
512 : : static int
513 : 375 : freq_edit_lower_bound(const vector<unsigned> & a, const vector<unsigned> & b)
514 : : {
515 : : int vec[VEC_SIZE];
516 : 375 : memset(vec, 0, sizeof(vec));
517 : 375 : vector<unsigned>::const_iterator i;
518 [ + + ]: 2205 : for (i = a.begin(); i != a.end(); ++i) {
519 : 1830 : ++vec[(*i) % VEC_SIZE];
520 : : }
521 [ + + ]: 2254 : for (i = b.begin(); i != b.end(); ++i) {
522 : 1879 : --vec[(*i) % VEC_SIZE];
523 : : }
524 : 375 : unsigned int total = 0;
525 [ + + ]: 24375 : for (size_t j = 0; j < VEC_SIZE; ++j) {
526 : 24000 : total += abs(vec[j]);
527 : : }
528 : : // Each insertion or deletion adds at most 1 to total. Each transposition
529 : : // doesn't change it at all. But each substitution can change it by 2 so
530 : : // we need to divide it by 2. Rounding up is OK, since the odd change must
531 : : // be due to an actual edit.
532 : 375 : return (total + 1) / 2;
533 : : }
534 : :
535 : : // Word must have a trigram score at least this close to the best score seen
536 : : // so far.
537 : : #define TRIGRAM_SCORE_THRESHOLD 2
538 : :
539 : : string
540 : 325 : Database::get_spelling_suggestion(const string &word,
541 : : unsigned max_edit_distance) const
542 : : {
543 : : LOGCALL(API, string, "Database::get_spelling_suggestion", word | max_edit_distance);
544 [ + + ]: 325 : if (word.size() <= 1) return string();
545 : 319 : AutoPtr<TermList> merger;
546 [ + + ]: 667 : for (size_t i = 0; i < internal.size(); ++i) {
547 : 348 : TermList * tl = internal[i]->open_spelling_termlist(word);
548 : : LOGLINE(SPELLING, "Sub db " << i << " tl = " << (void*)tl);
549 [ + + ]: 348 : if (tl) {
550 [ + + ]: 293 : if (merger.get()) {
551 : 13 : merger.reset(new OrTermList(merger.release(), tl));
552 : : } else {
553 : 280 : merger.reset(tl);
554 : : }
555 : : }
556 : : }
557 [ + + ]: 319 : if (!merger.get()) RETURN(string());
558 : :
559 : : // Convert word to UTF-32.
560 : : #if ! defined __SUNPRO_CC || __SUNPRO_CC - 0 >= 0x580
561 : : // Extra brackets needed to avoid this being misparsed as a function
562 : : // prototype.
563 : 280 : vector<unsigned> utf32_word((Utf8Iterator(word)), Utf8Iterator());
564 : : #else
565 : : // Older versions of Sun's C++ compiler need this workaround, but 5.8
566 : : // doesn't. Unsure of the exact version it was fixed in.
567 : : vector<unsigned> utf32_word;
568 : : for (Utf8Iterator sunpro_it(word); sunpro_it != Utf8Iterator(); ++sunpro_it) {
569 : : utf32_word.push_back(*sunpro_it);
570 : : }
571 : : #endif
572 : :
573 : 280 : vector<unsigned> utf32_term;
574 : :
575 : 280 : Xapian::termcount best = 1;
576 : 280 : string result;
577 : 280 : int edist_best = max_edit_distance;
578 : 280 : Xapian::doccount freq_best = 0;
579 : 280 : Xapian::doccount freq_exact = 0;
580 : 396 : while (true) {
581 : 676 : TermList *ret = merger->next();
582 [ + + ]: 676 : if (ret) merger.reset(ret);
583 : :
584 [ + + ]: 676 : if (merger->at_end()) break;
585 : :
586 : 396 : string term = merger->get_termname();
587 : 396 : Xapian::termcount score = merger->get_wdf();
588 : :
589 : : LOGLINE(SPELLING, "Term \"" << term << "\" ngram score " << score);
590 [ + + ]: 396 : if (score + TRIGRAM_SCORE_THRESHOLD >= best) {
591 [ + + ]: 378 : if (score > best) best = score;
592 : :
593 : : // There's no point considering a word where the difference
594 : : // in length is greater than the smallest number of edits we've
595 : : // found so far.
596 : :
597 : : // First check the length of the encoded UTF-8 version of term.
598 : : // Each UTF-32 character is 1-4 bytes in UTF-8.
599 [ - + ]: 378 : if (abs(long(term.size()) - long(word.size())) > edist_best * 4) {
600 : : LOGLINE(SPELLING, "Lengths much too different");
601 : 0 : continue;
602 : : }
603 : :
604 : : // Now convert to UTF-32, and compare the true lengths more
605 : : // strictly.
606 : 378 : utf32_term.assign(Utf8Iterator(term), Utf8Iterator());
607 : :
608 [ + + ]: 378 : if (abs(long(utf32_term.size()) - long(utf32_word.size()))
609 : : > edist_best) {
610 : : LOGLINE(SPELLING, "Lengths too different");
611 : 3 : continue;
612 : : }
613 : :
614 [ + + ]: 375 : if (freq_edit_lower_bound(utf32_term, utf32_word) > edist_best) {
615 : : LOGLINE(SPELLING, "Rejected by character frequency test");
616 : 15 : continue;
617 : : }
618 : :
619 : : int edist = edit_distance_unsigned(&utf32_term[0],
620 : : int(utf32_term.size()),
621 : : &utf32_word[0],
622 : : int(utf32_word.size()),
623 : 360 : edist_best);
624 : : LOGLINE(SPELLING, "Edit distance " << edist);
625 : :
626 [ + + ]: 360 : if (edist <= edist_best) {
627 : 345 : Xapian::doccount freq = 0;
628 [ + + ]: 718 : for (size_t j = 0; j < internal.size(); ++j)
629 : 373 : freq += internal[j]->get_spelling_frequency(term);
630 : :
631 : : LOGLINE(SPELLING, "Freq " << freq << " best " << freq_best);
632 : : // Even if we have an exact match, there may be a much more
633 : : // frequent potential correction which will still be
634 : : // interesting.
635 [ + + ]: 345 : if (edist == 0) {
636 : 25 : freq_exact = freq;
637 : 25 : continue;
638 : : }
639 : :
640 [ + + ][ + + ]: 320 : if (edist < edist_best || freq > freq_best) {
641 : : LOGLINE(SPELLING, "Best so far: \"" << term <<
642 : : "\" edist " << edist << " freq " << freq);
643 : 292 : result = term;
644 : 292 : edist_best = edist;
645 : 353 : freq_best = freq;
646 : : }
647 : : }
648 : : }
649 : : }
650 [ + + ]: 280 : if (freq_best < freq_exact)
651 : 16 : RETURN(string());
652 : 325 : RETURN(result);
653 : : }
654 : :
655 : : TermIterator
656 : 12 : Database::spellings_begin() const
657 : : {
658 : : LOGCALL(API, TermIterator, "Database::spellings_begin", NO_ARGS);
659 : 12 : AutoPtr<TermList> merger;
660 [ + + ]: 27 : for (size_t i = 0; i < internal.size(); ++i) {
661 : 15 : TermList * tl = internal[i]->open_spelling_wordlist();
662 [ + - ]: 15 : if (tl) {
663 [ + + ]: 15 : if (merger.get()) {
664 : 3 : merger.reset(new FreqAdderOrTermList(merger.release(), tl));
665 : : } else {
666 : 12 : merger.reset(tl);
667 : : }
668 : : }
669 : : }
670 : 12 : RETURN(TermIterator(merger.release()));
671 : : }
672 : :
673 : : TermIterator
674 : 30560 : Database::synonyms_begin(const std::string &term) const
675 : : {
676 : : LOGCALL(API, TermIterator, "Database::synonyms_begin", term);
677 : 30560 : AutoPtr<TermList> merger;
678 [ + + ]: 61135 : for (size_t i = 0; i < internal.size(); ++i) {
679 : 30575 : TermList * tl = internal[i]->open_synonym_termlist(term);
680 [ + + ]: 30575 : if (tl) {
681 [ - + ]: 15180 : if (merger.get()) {
682 : 0 : merger.reset(new OrTermList(merger.release(), tl));
683 : : } else {
684 : 15180 : merger.reset(tl);
685 : : }
686 : : }
687 : : }
688 : 30560 : RETURN(TermIterator(merger.release()));
689 : : }
690 : :
691 : : TermIterator
692 : 10148 : Database::synonym_keys_begin(const std::string &prefix) const
693 : : {
694 : : LOGCALL(API, TermIterator, "Database::synonyms_keys_begin", prefix);
695 : 10148 : AutoPtr<TermList> merger;
696 [ + + ]: 20299 : for (size_t i = 0; i < internal.size(); ++i) {
697 : 10151 : TermList * tl = internal[i]->open_synonym_keylist(prefix);
698 [ + + ]: 10151 : if (tl) {
699 [ - + ]: 10145 : if (merger.get()) {
700 : 0 : merger.reset(new OrTermList(merger.release(), tl));
701 : : } else {
702 : 10145 : merger.reset(tl);
703 : : }
704 : : }
705 : : }
706 : 10148 : RETURN(TermIterator(merger.release()));
707 : : }
708 : :
709 : : string
710 : 377 : Database::get_metadata(const string & key) const
711 : : {
712 : : LOGCALL(API, string, "Database::get_metadata", key);
713 [ + + ]: 377 : if (key.empty())
714 : 20 : throw InvalidArgumentError("Empty metadata keys are invalid");
715 [ + + ]: 357 : if (internal.empty()) RETURN(std::string());
716 : 357 : RETURN(internal[0]->get_metadata(key));
717 : : }
718 : :
719 : : Xapian::TermIterator
720 : 122 : Database::metadata_keys_begin(const std::string &prefix) const
721 : : {
722 : : LOGCALL(API, Xapian::TermIterator, "Database::metadata_keys_begin", NO_ARGS);
723 [ + + ]: 122 : if (internal.empty()) RETURN(TermIterator(NULL));
724 : 122 : RETURN(TermIterator(internal[0]->open_metadata_keylist(prefix)));
725 : : }
726 : :
727 : : std::string
728 : 3927 : Database::get_uuid() const
729 : : {
730 : : LOGCALL(API, std::string, "Database::get_uuid", NO_ARGS);
731 : 3927 : string uuid;
732 [ + + ][ + + ]: 7884 : for (size_t i = 0; i < internal.size(); ++i) {
733 : 3957 : string sub_uuid = internal[i]->get_uuid();
734 : : // If any of the sub-databases have no uuid, we can't make a uuid for
735 : : // the combined database.
736 [ + + ]: 3957 : if (sub_uuid.empty())
737 : 9 : RETURN(sub_uuid);
738 [ + + ]: 3948 : if (!uuid.empty()) uuid += ':';
739 : 3948 : uuid += sub_uuid;
740 : : }
741 : 3927 : RETURN(uuid);
742 : : }
743 : :
744 : : ///////////////////////////////////////////////////////////////////////////
745 : :
746 : 290 : WritableDatabase::WritableDatabase() : Database()
747 : : {
748 : : LOGCALL_CTOR(API, "WritableDatabase", NO_ARGS);
749 : 290 : }
750 : :
751 : 1608 : WritableDatabase::WritableDatabase(Database::Internal *internal_)
752 : 1608 : : Database(internal_)
753 : : {
754 : : LOGCALL_CTOR(API, "WritableDatabase", internal_);
755 : 1608 : }
756 : :
757 : 11 : WritableDatabase::WritableDatabase(const WritableDatabase &other)
758 : 11 : : Database(other)
759 : : {
760 : : LOGCALL_CTOR(API, "WritableDatabase", other);
761 : 11 : }
762 : :
763 : : void
764 : 479 : WritableDatabase::operator=(const WritableDatabase &other)
765 : : {
766 : : LOGCALL_VOID(API, "WritableDatabase::operator=", other);
767 : 479 : Database::operator=(other);
768 : 479 : }
769 : :
770 : 2946 : WritableDatabase::~WritableDatabase()
771 : : {
772 : : LOGCALL_DTOR(API, "WritableDatabase");
773 [ + - ][ - + ]: 2946 : }
[ # # ]
774 : :
775 : : XAPIAN_NORETURN(static void only_one_subdatabase_allowed());
776 : 1 : static void only_one_subdatabase_allowed()
777 : : {
778 : 1 : throw Xapian::InvalidOperationError("WritableDatabase needs exactly one subdatabase");
779 : : }
780 : :
781 : : void
782 : 2004 : WritableDatabase::commit()
783 : : {
784 : : LOGCALL_VOID(API, "WritableDatabase::commit", NO_ARGS);
785 [ - + ]: 2004 : if (internal.size() != 1) only_one_subdatabase_allowed();
786 : 2004 : internal[0]->commit();
787 : 1983 : }
788 : :
789 : : void
790 : 139 : WritableDatabase::begin_transaction(bool flushed)
791 : : {
792 : : LOGCALL_VOID(API, "WritableDatabase::begin_transaction", NO_ARGS);
793 [ - + ]: 139 : if (internal.size() != 1) only_one_subdatabase_allowed();
794 : 139 : internal[0]->begin_transaction(flushed);
795 : 103 : }
796 : :
797 : : void
798 : 54 : WritableDatabase::commit_transaction()
799 : : {
800 : : LOGCALL_VOID(API, "WritableDatabase::commit_transaction", NO_ARGS);
801 [ - + ]: 54 : if (internal.size() != 1) only_one_subdatabase_allowed();
802 : 54 : internal[0]->commit_transaction();
803 : 27 : }
804 : :
805 : : void
806 : 97 : WritableDatabase::cancel_transaction()
807 : : {
808 : : LOGCALL_VOID(API, "WritableDatabase::cancel_transaction", NO_ARGS);
809 [ - + ]: 97 : if (internal.size() != 1) only_one_subdatabase_allowed();
810 : 97 : internal[0]->cancel_transaction();
811 : 70 : }
812 : :
813 : :
814 : : Xapian::docid
815 : 272711 : WritableDatabase::add_document(const Document & document)
816 : : {
817 : : LOGCALL(API, Xapian::docid, "WritableDatabase::add_document", document);
818 [ - + ]: 272711 : if (internal.size() != 1) only_one_subdatabase_allowed();
819 : 272711 : RETURN(internal[0]->add_document(document));
820 : : }
821 : :
822 : : void
823 : 48398 : WritableDatabase::delete_document(Xapian::docid did)
824 : : {
825 : : LOGCALL_VOID(API, "WritableDatabase::delete_document", did);
826 [ - + ]: 48398 : if (internal.size() != 1) only_one_subdatabase_allowed();
827 [ - + ]: 48398 : if (did == 0)
828 : 0 : docid_zero_invalid();
829 : 48398 : internal[0]->delete_document(did);
830 : 48382 : }
831 : :
832 : : void
833 : 69 : WritableDatabase::delete_document(const std::string & unique_term)
834 : : {
835 : : LOGCALL_VOID(API, "WritableDatabase::delete_document", unique_term);
836 [ - + ]: 69 : if (internal.size() != 1) only_one_subdatabase_allowed();
837 [ - + ]: 69 : if (unique_term.empty())
838 : 0 : throw InvalidArgumentError("Empty termnames are invalid");
839 : 69 : internal[0]->delete_document(unique_term);
840 : 69 : }
841 : :
842 : : void
843 : 79207 : WritableDatabase::replace_document(Xapian::docid did, const Document & document)
844 : : {
845 : : LOGCALL_VOID(API, "WritableDatabase::replace_document", did | document);
846 [ - + ]: 79207 : if (internal.size() != 1) only_one_subdatabase_allowed();
847 [ + + ]: 79207 : if (did == 0)
848 : 10 : docid_zero_invalid();
849 : 79197 : internal[0]->replace_document(did, document);
850 : 79191 : }
851 : :
852 : : Xapian::docid
853 : 304 : WritableDatabase::replace_document(const std::string & unique_term,
854 : : const Document & document)
855 : : {
856 : : LOGCALL(API, Xapian::docid, "WritableDatabase::replace_document", unique_term | document);
857 [ - + ]: 304 : if (internal.size() != 1) only_one_subdatabase_allowed();
858 [ - + ]: 304 : if (unique_term.empty())
859 : 0 : throw InvalidArgumentError("Empty termnames are invalid");
860 : 304 : RETURN(internal[0]->replace_document(unique_term, document));
861 : : }
862 : :
863 : : void
864 : 178 : WritableDatabase::add_spelling(const std::string & word,
865 : : Xapian::termcount freqinc) const
866 : : {
867 : : LOGCALL_VOID(API, "WritableDatabase::add_spelling", word | freqinc);
868 [ + + ]: 178 : if (internal.size() != 1) only_one_subdatabase_allowed();
869 : 177 : internal[0]->add_spelling(word, freqinc);
870 : 177 : }
871 : :
872 : : void
873 : 144 : WritableDatabase::remove_spelling(const std::string & word,
874 : : Xapian::termcount freqdec) const
875 : : {
876 : : LOGCALL_VOID(API, "WritableDatabase::remove_spelling", word | freqdec);
877 [ - + ]: 144 : if (internal.size() != 1) only_one_subdatabase_allowed();
878 : 144 : internal[0]->remove_spelling(word, freqdec);
879 : 144 : }
880 : :
881 : : void
882 : 295 : WritableDatabase::add_synonym(const std::string & term,
883 : : const std::string & synonym) const
884 : : {
885 : : LOGCALL_VOID(API, "WritableDatabase::add_synonym", term | synonym);
886 [ - + ]: 295 : if (internal.size() != 1) only_one_subdatabase_allowed();
887 : 295 : internal[0]->add_synonym(term, synonym);
888 : 295 : }
889 : :
890 : : void
891 : 3 : WritableDatabase::remove_synonym(const std::string & term,
892 : : const std::string & synonym) const
893 : : {
894 : : LOGCALL_VOID(API, "WritableDatabase::remove_synonym", term | synonym);
895 [ - + ]: 3 : if (internal.size() != 1) only_one_subdatabase_allowed();
896 : 3 : internal[0]->remove_synonym(term, synonym);
897 : 3 : }
898 : :
899 : : void
900 : 3 : WritableDatabase::clear_synonyms(const std::string & term) const
901 : : {
902 : : LOGCALL_VOID(API, "WritableDatabase::clear_synonyms", term);
903 [ - + ]: 3 : if (internal.size() != 1) only_one_subdatabase_allowed();
904 : 3 : internal[0]->clear_synonyms(term);
905 : 3 : }
906 : :
907 : : void
908 : 303 : WritableDatabase::set_metadata(const string & key, const string & value)
909 : : {
910 : : LOGCALL_VOID(API, "WritableDatabase::set_metadata", key | value);
911 [ - + ]: 303 : if (internal.size() != 1) only_one_subdatabase_allowed();
912 [ + + ]: 303 : if (key.empty())
913 : 10 : throw InvalidArgumentError("Empty metadata keys are invalid");
914 : 293 : internal[0]->set_metadata(key, value);
915 : 293 : }
916 : :
917 : : string
918 : 1 : WritableDatabase::get_description() const
919 : : {
920 : : /// \todo display contents of the writable database
921 : 1 : return "WritableDatabase()";
922 : : }
923 : :
924 : : }
|