Branch data Line data Source code
1 : : /* chert_database.cc: chert database
2 : : *
3 : : * Copyright 1999,2000,2001 BrightStation PLC
4 : : * Copyright 2001 Hein Ragas
5 : : * Copyright 2002 Ananova Ltd
6 : : * Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011 Olly Betts
7 : : * Copyright 2006,2008 Lemur Consulting Ltd
8 : : * Copyright 2009,2010 Richard Boulton
9 : : * Copyright 2009 Kan-Ru Chen
10 : : * Copyright 2011 Dan Colish
11 : : *
12 : : * This program is free software; you can redistribute it and/or
13 : : * modify it under the terms of the GNU General Public License as
14 : : * published by the Free Software Foundation; either version 2 of the
15 : : * License, or (at your option) any later version.
16 : : *
17 : : * This program is distributed in the hope that it will be useful,
18 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 : : * GNU General Public License for more details.
21 : : *
22 : : * You should have received a copy of the GNU General Public License
23 : : * along with this program; if not, write to the Free Software
24 : : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
25 : : * USA
26 : : */
27 : :
28 : : #include <config.h>
29 : :
30 : : #include "chert_database.h"
31 : :
32 : : #include <xapian/error.h>
33 : : #include <xapian/valueiterator.h>
34 : :
35 : : #include "contiguousalldocspostlist.h"
36 : : #include "chert_alldocsmodifiedpostlist.h"
37 : : #include "chert_alldocspostlist.h"
38 : : #include "chert_alltermslist.h"
39 : : #include "chert_replicate_internal.h"
40 : : #include "chert_document.h"
41 : : #include "../flint_lock.h"
42 : : #include "chert_metadata.h"
43 : : #include "chert_modifiedpostlist.h"
44 : : #include "chert_positionlist.h"
45 : : #include "chert_postlist.h"
46 : : #include "chert_record.h"
47 : : #include "chert_spellingwordslist.h"
48 : : #include "chert_termlist.h"
49 : : #include "chert_valuelist.h"
50 : : #include "chert_values.h"
51 : : #include "debuglog.h"
52 : : #include "io_utils.h"
53 : : #include "pack.h"
54 : : #include "remoteconnection.h"
55 : : #include "replicate_utils.h"
56 : : #include "replication.h"
57 : : #include "replicationprotocol.h"
58 : : #include "serialise.h"
59 : : #include "str.h"
60 : : #include "stringutils.h"
61 : : #include "utils.h"
62 : : #include "valuestats.h"
63 : :
64 : : #ifdef __WIN32__
65 : : # include "msvc_posix_wrapper.h"
66 : : #endif
67 : :
68 : : #include "safeerrno.h"
69 : : #include "safesysstat.h"
70 : : #include <sys/types.h>
71 : :
72 : : #include <algorithm>
73 : : #include "autoptr.h"
74 : : #include <string>
75 : :
76 : : using namespace std;
77 : : using namespace Xapian;
78 : :
79 : : // The maximum safe term length is determined by the postlist. There we
80 : : // store the term using pack_string_preserving_sort() which takes the
81 : : // length of the string plus an extra byte (assuming the string doesn't
82 : : // contain any zero bytes), followed by the docid with encoded with
83 : : // pack_uint_preserving_sort() which takes up to 5 bytes.
84 : : //
85 : : // The Btree manager's key length limit is 252 bytes so the maximum safe term
86 : : // length is 252 - 1 - 5 = 246 bytes. We use 245 rather than 246 for
87 : : // consistency with flint.
88 : : //
89 : : // If the term contains zero bytes, the limit is lower (by one for each zero
90 : : // byte in the term).
91 : : #define MAX_SAFE_TERM_LENGTH 245
92 : :
93 : : /** Maximum number of times to try opening the tables to get them at a
94 : : * consistent revision.
95 : : *
96 : : * This is mostly just to avoid any chance of an infinite loop - normally
97 : : * we'll either get then on the first or second try.
98 : : */
99 : : const int MAX_OPEN_RETRIES = 100;
100 : :
101 : : /* This finds the tables, opens them at consistent revisions, manages
102 : : * determining the current and next revision numbers, and stores handles
103 : : * to the tables.
104 : : */
105 : 2289 : ChertDatabase::ChertDatabase(const string &chert_dir, int action,
106 : : unsigned int block_size)
107 : : : db_dir(chert_dir),
108 : : readonly(action == XAPIAN_DB_READONLY),
109 : : version_file(db_dir),
110 : : postlist_table(db_dir, readonly),
111 : : position_table(db_dir, readonly),
112 : : termlist_table(db_dir, readonly),
113 : : value_manager(&postlist_table, &termlist_table),
114 : : synonym_table(db_dir, readonly),
115 : : spelling_table(db_dir, readonly),
116 : : record_table(db_dir, readonly),
117 : : lock(db_dir),
118 : 2289 : max_changesets(0)
119 : : {
120 : : LOGCALL_CTOR(DB, "ChertDatabase", chert_dir | action | block_size);
121 : :
122 [ + - - + ]: 2289 : if (action == XAPIAN_DB_READONLY) {
123 : 1608 : open_tables_consistent();
124 : 1608 : return;
125 : : }
126 : :
127 [ # # ][ # # ]: 681 : if (action != Xapian::DB_OPEN && !database_exists()) {
[ # # ][ + + ]
[ + + ][ + + ]
128 : :
129 : : // Create the directory for the database, if it doesn't exist
130 : : // already.
131 : 332 : bool fail = false;
132 : : struct stat statbuf;
133 [ # # ][ + + ]: 332 : if (stat(db_dir, &statbuf) == 0) {
134 [ # # ][ - + ]: 272 : if (!S_ISDIR(statbuf.st_mode)) fail = true;
135 [ # # ][ # # ]: 60 : } else if (errno != ENOENT || mkdir(db_dir, 0755) == -1) {
[ # # ][ + - ]
[ - + ][ - + ]
136 : 0 : fail = true;
137 : : }
138 [ # # ][ - + ]: 332 : if (fail) {
139 : : throw Xapian::DatabaseCreateError("Cannot create directory `" +
140 : 0 : db_dir + "'", errno);
141 : : }
142 : 332 : get_database_write_lock(true);
143 : :
144 : 332 : create_and_open_tables(block_size);
145 : 332 : return;
146 : : }
147 : :
148 [ # # ][ - + ]: 349 : if (action == Xapian::DB_CREATE) {
149 : : throw Xapian::DatabaseCreateError("Can't create new database at `" +
150 : : db_dir + "': a database already exists and I was told "
151 : 0 : "not to overwrite it");
152 : : }
153 : :
154 : 349 : get_database_write_lock(false);
155 : : // if we're overwriting, pretend the db doesn't exist
156 [ # # + + ]: 345 : if (action == Xapian::DB_CREATE_OR_OVERWRITE) {
157 : 2 : create_and_open_tables(block_size);
158 : 2 : return;
159 : : }
160 : :
161 : : // Get latest consistent version
162 : 343 : open_tables_consistent();
163 : :
164 : : // Check that there are no more recent versions of tables. If there
165 : : // are, perform recovery by writing a new revision number to all
166 : : // tables.
167 [ # # - + ]: 343 : if (record_table.get_open_revision_number() !=
168 : : postlist_table.get_latest_revision_number()) {
169 : 0 : chert_revision_number_t new_revision = get_next_revision_number();
170 : :
171 : 0 : set_revision_number(new_revision);
172 : : }
173 : 2329 : }
174 : :
175 : 2285 : ChertDatabase::~ChertDatabase()
176 : : {
177 : : LOGCALL_DTOR(DB, "~ChertDatabase");
178 [ + - ][ # # ]: 2285 : }
[ - + ]
179 : :
180 : : bool
181 : 474 : ChertDatabase::database_exists() {
182 : : LOGCALL(DB, bool, "ChertDatabase::database_exists", NO_ARGS);
183 [ + + ][ + - ]: 474 : RETURN(record_table.exists() && postlist_table.exists());
184 : : }
185 : :
186 : : void
187 : 334 : ChertDatabase::create_and_open_tables(unsigned int block_size)
188 : : {
189 : : LOGCALL_VOID(DB, "ChertDatabase::create_and_open_tables", NO_ARGS);
190 : : // The caller is expected to create the database directory if it doesn't
191 : : // already exist.
192 : :
193 : : // Create postlist_table first, and record_table last. Existence of
194 : : // record_table is considered to imply existence of the database.
195 : 334 : version_file.create();
196 : 334 : postlist_table.create_and_open(block_size);
197 : 334 : position_table.create_and_open(block_size);
198 : 334 : termlist_table.create_and_open(block_size);
199 : 334 : synonym_table.create_and_open(block_size);
200 : 334 : spelling_table.create_and_open(block_size);
201 : 334 : record_table.create_and_open(block_size);
202 : :
203 : : Assert(database_exists());
204 : :
205 : : // Check consistency
206 : 334 : chert_revision_number_t revision = record_table.get_open_revision_number();
207 [ - + ]: 334 : if (revision != postlist_table.get_open_revision_number()) {
208 : 0 : throw Xapian::DatabaseCreateError("Newly created tables are not in consistent state");
209 : : }
210 : :
211 : 334 : stats.zero();
212 : 334 : }
213 : :
214 : : void
215 : 2357 : ChertDatabase::open_tables_consistent()
216 : : {
217 : : LOGCALL_VOID(DB, "ChertDatabase::open_tables_consistent", NO_ARGS);
218 : : // Open record_table first, since it's the last to be written to,
219 : : // and hence if a revision is available in it, it should be available
220 : : // in all the other tables (unless they've moved on already).
221 : : //
222 : : // If we find that a table can't open the desired revision, we
223 : : // go back and open record_table again, until record_table has
224 : : // the same revision as the last time we opened it.
225 : :
226 : 2357 : chert_revision_number_t cur_rev = record_table.get_open_revision_number();
227 : :
228 : : // Check the version file unless we're reopening.
229 [ + + ]: 2357 : if (cur_rev == 0) version_file.read_and_check();
230 : :
231 : 2357 : record_table.open();
232 : 2353 : chert_revision_number_t revision = record_table.get_open_revision_number();
233 : :
234 [ + + + + ]: 2353 : if (cur_rev && cur_rev == revision) {
235 : : // We're reopening a database and the revision hasn't changed so we
236 : : // don't need to do anything.
237 : 374 : return;
238 : : }
239 : :
240 : : // Set the block_size for optional tables as they may not currently exist.
241 : 1979 : unsigned int block_size = record_table.get_block_size();
242 : 1979 : position_table.set_block_size(block_size);
243 : 1979 : termlist_table.set_block_size(block_size);
244 : 1979 : synonym_table.set_block_size(block_size);
245 : 1979 : spelling_table.set_block_size(block_size);
246 : :
247 : 1979 : value_manager.reset();
248 : :
249 : 1979 : bool fully_opened = false;
250 : 1979 : int tries_left = MAX_OPEN_RETRIES;
251 [ + + ][ + - ]: 3958 : while (!fully_opened && (tries_left--) > 0) {
[ + + ]
252 [ + - ][ + - ]: 1979 : if (spelling_table.open(revision) &&
[ + - ][ + - ]
[ + - ][ + - ]
253 : : synonym_table.open(revision) &&
254 : : termlist_table.open(revision) &&
255 : : position_table.open(revision) &&
256 : : postlist_table.open(revision)) {
257 : : // Everything now open at the same revision.
258 : 1979 : fully_opened = true;
259 : : } else {
260 : : // Couldn't open consistent revision: two cases possible:
261 : : // i) An update has completed and a second one has begun since
262 : : // record was opened. This leaves a consistent revision
263 : : // available, but not the one we were trying to open.
264 : : // ii) Tables have become corrupt / have no consistent revision
265 : : // available. In this case, updates must have ceased.
266 : : //
267 : : // So, we reopen the record table, and check its revision number,
268 : : // if it's changed we try the opening again, otherwise we give up.
269 : : //
270 : 0 : record_table.open();
271 : : chert_revision_number_t newrevision =
272 : 0 : record_table.get_open_revision_number();
273 [ # # ]: 0 : if (revision == newrevision) {
274 : : // Revision number hasn't changed - therefore a second index
275 : : // sweep hasn't begun and the system must have failed. Database
276 : : // is inconsistent.
277 : 0 : throw Xapian::DatabaseCorruptError("Cannot open tables at consistent revisions");
278 : : }
279 : 0 : revision = newrevision;
280 : : }
281 : : }
282 : :
283 [ - + ]: 1979 : if (!fully_opened) {
284 : 0 : throw Xapian::DatabaseModifiedError("Cannot open tables at stable revision - changing too fast");
285 : : }
286 : :
287 : 2353 : stats.read(postlist_table);
288 : : }
289 : :
290 : : void
291 : 0 : ChertDatabase::open_tables(chert_revision_number_t revision)
292 : : {
293 : : LOGCALL_VOID(DB, "ChertDatabase::open_tables", revision);
294 : 0 : version_file.read_and_check();
295 : 0 : record_table.open(revision);
296 : :
297 : : // Set the block_size for optional tables as they may not currently exist.
298 : 0 : unsigned int block_size = record_table.get_block_size();
299 : 0 : position_table.set_block_size(block_size);
300 : 0 : termlist_table.set_block_size(block_size);
301 : 0 : synonym_table.set_block_size(block_size);
302 : 0 : spelling_table.set_block_size(block_size);
303 : :
304 : 0 : value_manager.reset();
305 : :
306 : 0 : spelling_table.open(revision);
307 : 0 : synonym_table.open(revision);
308 : 0 : termlist_table.open(revision);
309 : 0 : position_table.open(revision);
310 : 0 : postlist_table.open(revision);
311 : 0 : }
312 : :
313 : : chert_revision_number_t
314 : 684 : ChertDatabase::get_revision_number() const
315 : : {
316 : : LOGCALL(DB, chert_revision_number_t, "ChertDatabase::get_revision_number", NO_ARGS);
317 : : // We could use any table here, theoretically.
318 : 684 : RETURN(postlist_table.get_open_revision_number());
319 : : }
320 : :
321 : : chert_revision_number_t
322 : 486 : ChertDatabase::get_next_revision_number() const
323 : : {
324 : : LOGCALL(DB, chert_revision_number_t, "ChertDatabase::get_next_revision_number", NO_ARGS);
325 : : /* We _must_ use postlist_table here, since it is always the first
326 : : * to be written, and hence will have the greatest available revision
327 : : * number.
328 : : */
329 : : chert_revision_number_t new_revision =
330 : 486 : postlist_table.get_latest_revision_number();
331 : 486 : ++new_revision;
332 : 486 : RETURN(new_revision);
333 : : }
334 : :
335 : : void
336 : 12 : ChertDatabase::get_changeset_revisions(const string & path,
337 : : chert_revision_number_t * startrev,
338 : : chert_revision_number_t * endrev) const
339 : : {
340 : 12 : int changes_fd = -1;
341 : : #ifdef __WIN32__
342 : : changes_fd = msvc_posix_open(path.c_str(), O_RDONLY);
343 : : #else
344 : 12 : changes_fd = open(path.c_str(), O_RDONLY);
345 : : #endif
346 : 12 : fdcloser closer(changes_fd);
347 : :
348 [ - + ]: 12 : if (changes_fd < 0) {
349 : : string message = string("Couldn't open changeset ")
350 : 0 : + path + " to read";
351 : 0 : throw Xapian::DatabaseError(message, errno);
352 : : }
353 : :
354 : : char buf[REASONABLE_CHANGESET_SIZE];
355 : 12 : const char *start = buf;
356 : : const char *end = buf + io_read(changes_fd, buf,
357 : 12 : REASONABLE_CHANGESET_SIZE, 0);
358 [ - + ]: 12 : if (strncmp(start, CHANGES_MAGIC_STRING,
359 : : CONST_STRLEN(CHANGES_MAGIC_STRING)) != 0) {
360 : : string message = string("Changeset at ")
361 : 0 : + path + " does not contain valid magic string";
362 : 0 : throw Xapian::DatabaseError(message);
363 : : }
364 : 12 : start += CONST_STRLEN(CHANGES_MAGIC_STRING);
365 [ - + ]: 12 : if (start >= end)
366 : 0 : throw Xapian::DatabaseError("Changeset too short at " + path);
367 : :
368 : : unsigned int changes_version;
369 [ - + ]: 12 : if (!unpack_uint(&start, end, &changes_version))
370 : : throw Xapian::DatabaseError("Couldn't read a valid version number for "
371 : 0 : "changeset at " + path);
372 [ - + ]: 12 : if (changes_version != CHANGES_VERSION)
373 : : throw Xapian::DatabaseError("Don't support version of changeset at "
374 : 0 : + path);
375 : :
376 [ - + ]: 12 : if (!unpack_uint(&start, end, startrev))
377 : : throw Xapian::DatabaseError("Couldn't read a valid start revision from "
378 : 0 : "changeset at " + path);
379 : :
380 [ - + ]: 12 : if (!unpack_uint(&start, end, endrev))
381 : : throw Xapian::DatabaseError("Couldn't read a valid end revision for "
382 : 12 : "changeset at " + path);
383 : 12 : }
384 : :
385 : : void
386 : 486 : ChertDatabase::set_revision_number(chert_revision_number_t new_revision)
387 : : {
388 : : LOGCALL_VOID(DB, "ChertDatabase::set_revision_number", new_revision);
389 : :
390 : 486 : value_manager.merge_changes();
391 : :
392 : 486 : postlist_table.flush_db();
393 : 486 : position_table.flush_db();
394 : 486 : termlist_table.flush_db();
395 : 486 : synonym_table.flush_db();
396 : 486 : spelling_table.flush_db();
397 : 486 : record_table.flush_db();
398 : :
399 : 486 : int changes_fd = -1;
400 : 486 : string changes_name;
401 : :
402 : 486 : const char *p = getenv("XAPIAN_MAX_CHANGESETS");
403 [ + + ]: 486 : if (p) {
404 : 472 : max_changesets = atoi(p);
405 : : } else {
406 : 14 : max_changesets = 0;
407 : : }
408 : :
409 [ + + ]: 486 : if (max_changesets > 0) {
410 : 67 : chert_revision_number_t old_revision = get_revision_number();
411 [ + + ]: 67 : if (old_revision) {
412 : : // Don't generate a changeset for the first revision.
413 : : changes_fd = create_changeset_file(db_dir,
414 : : "/changes" + str(old_revision),
415 : 23 : changes_name);
416 : : }
417 : : }
418 : :
419 : : try {
420 : 486 : fdcloser closefd(changes_fd);
421 [ + + ]: 486 : if (changes_fd >= 0) {
422 : 23 : string buf;
423 : 23 : chert_revision_number_t old_revision = get_revision_number();
424 : 23 : buf += CHANGES_MAGIC_STRING;
425 : 23 : pack_uint(buf, CHANGES_VERSION);
426 : 23 : pack_uint(buf, old_revision);
427 : 23 : pack_uint(buf, new_revision);
428 : :
429 : : #ifndef DANGEROUS
430 : 23 : buf += '\x00'; // Changes can be applied to a live database.
431 : : #else
432 : : buf += '\x01';
433 : : #endif
434 : :
435 : 23 : io_write(changes_fd, buf.data(), buf.size());
436 : :
437 : : // Write the changes to the blocks in the tables. Do the postlist
438 : : // table last, so that ends up cached the most, if the cache
439 : : // available is limited. Do the position table just before that
440 : : // as having that cached will also improve search performance.
441 : 23 : termlist_table.write_changed_blocks(changes_fd);
442 : 23 : synonym_table.write_changed_blocks(changes_fd);
443 : 23 : spelling_table.write_changed_blocks(changes_fd);
444 : 23 : record_table.write_changed_blocks(changes_fd);
445 : 23 : position_table.write_changed_blocks(changes_fd);
446 : 23 : postlist_table.write_changed_blocks(changes_fd);
447 : : }
448 : :
449 : 486 : postlist_table.commit(new_revision, changes_fd);
450 : 486 : position_table.commit(new_revision, changes_fd);
451 : 486 : termlist_table.commit(new_revision, changes_fd);
452 : 486 : synonym_table.commit(new_revision, changes_fd);
453 : 486 : spelling_table.commit(new_revision, changes_fd);
454 : :
455 : 486 : string changes_tail; // Data to be appended to the changes file
456 [ + + ]: 486 : if (changes_fd >= 0) {
457 : 23 : changes_tail += '\0';
458 : 23 : pack_uint(changes_tail, new_revision);
459 : : }
460 : 486 : record_table.commit(new_revision, changes_fd, &changes_tail);
461 : :
462 : 0 : } catch (...) {
463 : : // Remove the changeset, if there was one.
464 [ # # ]: 0 : if (changes_fd >= 0) {
465 : 0 : (void)io_unlink(changes_name);
466 : : }
467 : :
468 : 0 : throw;
469 : : }
470 : :
471 [ + + + + ]: 486 : if (changes_fd >= 0 && max_changesets < new_revision) {
472 : : // While change sets less than N - max_changesets exist, delete them
473 : : // 1 must be subtracted so we don't delete the changeset we just wrote
474 : : // when max_changesets = 1
475 : 8 : unsigned rev = new_revision - max_changesets - 1;
476 [ + + ]: 12 : while (io_unlink(db_dir + "/changes" + str(rev--))) { }
477 : 486 : }
478 : 486 : }
479 : :
480 : : void
481 : 843 : ChertDatabase::reopen()
482 : : {
483 : : LOGCALL_VOID(DB, "ChertDatabase::reopen", NO_ARGS);
484 [ + + ]: 843 : if (readonly) open_tables_consistent();
485 : 839 : }
486 : :
487 : : void
488 : 15 : ChertDatabase::close()
489 : : {
490 : : LOGCALL_VOID(DB, "ChertDatabase::close", NO_ARGS);
491 : 15 : postlist_table.close(true);
492 : 15 : position_table.close(true);
493 : 15 : termlist_table.close(true);
494 : 15 : synonym_table.close(true);
495 : 15 : spelling_table.close(true);
496 : 15 : record_table.close(true);
497 : 15 : lock.release();
498 : 15 : }
499 : :
500 : : void
501 : 681 : ChertDatabase::get_database_write_lock(bool creating)
502 : : {
503 : : LOGCALL_VOID(DB, "ChertDatabase::get_database_write_lock", creating);
504 : 681 : string explanation;
505 : 681 : FlintLock::reason why = lock.lock(true, explanation);
506 [ + + ]: 681 : if (why != FlintLock::SUCCESS) {
507 [ - + ][ # # ]: 4 : if (why == FlintLock::UNKNOWN && !creating && !database_exists()) {
[ # # ][ - + ]
508 : 0 : string msg("No chert database found at path `");
509 : 0 : msg += db_dir;
510 : 0 : msg += '\'';
511 : 0 : throw Xapian::DatabaseOpeningError(msg);
512 : : }
513 : 4 : lock.throw_databaselockerror(why, db_dir, explanation);
514 : 681 : }
515 : 677 : }
516 : :
517 : : void
518 : 8 : ChertDatabase::send_whole_database(RemoteConnection & conn, double end_time)
519 : : {
520 : : LOGCALL_VOID(DB, "ChertDatabase::send_whole_database", conn | end_time);
521 : :
522 : : // Send the current revision number in the header.
523 : 8 : string buf;
524 : 8 : string uuid = get_uuid();
525 : 8 : buf += encode_length(uuid.size());
526 : 8 : buf += uuid;
527 : 8 : pack_uint(buf, get_revision_number());
528 : 8 : conn.send_message(REPL_REPLY_DB_HEADER, buf, end_time);
529 : :
530 : : // Send all the tables. The tables which we want to be cached best after
531 : : // the copy finished are sent last.
532 : : static const char filenames[] =
533 : : "\x0b""termlist.DB""\x0e""termlist.baseA\x0e""termlist.baseB"
534 : : "\x0a""synonym.DB""\x0d""synonym.baseA\x0d""synonym.baseB"
535 : : "\x0b""spelling.DB""\x0e""spelling.baseA\x0e""spelling.baseB"
536 : : "\x09""record.DB""\x0c""record.baseA\x0c""record.baseB"
537 : : "\x0b""position.DB""\x0e""position.baseA\x0e""position.baseB"
538 : : "\x0b""postlist.DB""\x0e""postlist.baseA\x0e""postlist.baseB"
539 : : "\x08""iamchert";
540 : 8 : string filepath = db_dir;
541 : 8 : filepath += '/';
542 [ + + ]: 160 : for (const char * p = filenames; *p; p += *p + 1) {
543 : 152 : string leaf(p + 1, size_t(static_cast<unsigned char>(*p)));
544 : 152 : filepath.replace(db_dir.size() + 1, string::npos, leaf);
545 : : #ifdef __WIN32__
546 : : int fd = msvc_posix_open(filepath.c_str(), O_RDONLY);
547 : : #else
548 : 152 : int fd = open(filepath.c_str(), O_RDONLY);
549 : : #endif
550 [ + + ]: 152 : if (fd > 0) {
551 : 101 : fdcloser closefd(fd);
552 : 101 : conn.send_message(REPL_REPLY_DB_FILENAME, leaf, end_time);
553 : 101 : conn.send_file(REPL_REPLY_DB_FILEDATA, fd, end_time);
554 : : }
555 : 8 : }
556 : 8 : }
557 : :
558 : : void
559 : 20 : ChertDatabase::write_changesets_to_fd(int fd,
560 : : const string & revision,
561 : : bool need_whole_db,
562 : : ReplicationInfo * info)
563 : : {
564 : : LOGCALL_VOID(DB, "ChertDatabase::write_changesets_to_fd", fd | revision | need_whole_db | info);
565 : :
566 : 20 : int whole_db_copies_left = MAX_DB_COPIES_PER_CONVERSATION;
567 : 20 : chert_revision_number_t start_rev_num = 0;
568 : 20 : string start_uuid = get_uuid();
569 : :
570 : 20 : chert_revision_number_t needed_rev_num = 0;
571 : :
572 : 20 : const char * rev_ptr = revision.data();
573 : 20 : const char * rev_end = rev_ptr + revision.size();
574 [ - + ]: 20 : if (!unpack_uint(&rev_ptr, rev_end, &start_rev_num)) {
575 : 0 : need_whole_db = true;
576 : : }
577 : :
578 : 20 : RemoteConnection conn(-1, fd, string());
579 : :
580 : : // While the starting revision number is less than the latest revision
581 : : // number, look for a changeset, and write it.
582 : : //
583 : : // FIXME - perhaps we should make hardlinks for all the changesets we're
584 : : // likely to need, first, and then start sending them, so that there's no
585 : : // risk of them disappearing while we're sending earlier ones.
586 : 23 : while (true) {
587 [ + + ]: 43 : if (need_whole_db) {
588 : : // Decrease the counter of copies left to be sent, and fail
589 : : // if we've already copied the database enough. This ensures that
590 : : // synchronisation attempts always terminate eventually.
591 [ - + ]: 8 : if (whole_db_copies_left == 0) {
592 : : conn.send_message(REPL_REPLY_FAIL,
593 : : "Database changing too fast",
594 : 0 : 0.0);
595 : : return;
596 : : }
597 : 8 : whole_db_copies_left--;
598 : :
599 : : // Send the whole database across.
600 : 8 : start_rev_num = get_revision_number();
601 : 8 : start_uuid = get_uuid();
602 : :
603 : 8 : send_whole_database(conn, 0.0);
604 [ + - ]: 8 : if (info != NULL)
605 : 8 : ++(info->fullcopy_count);
606 : :
607 : 8 : need_whole_db = false;
608 : :
609 : 8 : reopen();
610 [ + - ]: 8 : if (start_uuid == get_uuid()) {
611 : : // Send the latest revision number after sending the tables.
612 : : // The update must proceed to that revision number before the
613 : : // copy is safe to make live.
614 : :
615 : 8 : string buf;
616 : 8 : needed_rev_num = get_revision_number();
617 : 8 : pack_uint(buf, needed_rev_num);
618 : 8 : conn.send_message(REPL_REPLY_DB_FOOTER, buf, 0.0);
619 [ + - + - ]: 8 : if (info != NULL && start_rev_num == needed_rev_num)
620 : 8 : info->changed = true;
621 : : } else {
622 : : // Database has been replaced since we did the copy. Send a
623 : : // higher revision number than the revision we've just copied,
624 : : // so that the client doesn't make the copy we've just done
625 : : // live, and then mark that we need to do a copy again.
626 : : // The client will never actually get the required revision,
627 : : // because the next message is going to be the start of a new
628 : : // database transfer.
629 : :
630 : 0 : string buf;
631 : 0 : pack_uint(buf, start_rev_num + 1);
632 : 0 : conn.send_message(REPL_REPLY_DB_FOOTER, buf, 0.0);
633 : 0 : need_whole_db = true;
634 : : }
635 : : } else {
636 : : // Check if we've sent all the updates.
637 [ + + ]: 35 : if (start_rev_num >= get_revision_number()) {
638 : 20 : reopen();
639 [ - + ]: 20 : if (start_uuid != get_uuid()) {
640 : 0 : need_whole_db = true;
641 : 0 : continue;
642 : : }
643 [ + - ]: 20 : if (start_rev_num >= get_revision_number()) {
644 : : break;
645 : : }
646 : : }
647 : :
648 : : // Look for the changeset for revision start_rev_num.
649 : 15 : string changes_name = db_dir + "/changes" + str(start_rev_num);
650 : : #ifdef __WIN32__
651 : : int fd_changes = msvc_posix_open(changes_name.c_str(), O_RDONLY);
652 : : #else
653 : 15 : int fd_changes = open(changes_name.c_str(), O_RDONLY);
654 : : #endif
655 [ + + ]: 15 : if (fd_changes > 0) {
656 : 12 : fdcloser closefd(fd_changes);
657 : :
658 : : // Send it, and also update start_rev_num to the new value
659 : : // specified in the changeset.
660 : : chert_revision_number_t changeset_start_rev_num;
661 : : chert_revision_number_t changeset_end_rev_num;
662 : : get_changeset_revisions(changes_name,
663 : : &changeset_start_rev_num,
664 : 12 : &changeset_end_rev_num);
665 [ - + ]: 12 : if (changeset_start_rev_num != start_rev_num) {
666 : 0 : throw Xapian::DatabaseError("Changeset start revision does not match changeset filename");
667 : : }
668 [ - + ]: 12 : if (changeset_start_rev_num >= changeset_end_rev_num) {
669 : 0 : throw Xapian::DatabaseError("Changeset start revision is not less than end revision");
670 : : }
671 : :
672 : 12 : conn.send_file(REPL_REPLY_CHANGESET, fd_changes, 0.0);
673 : 12 : start_rev_num = changeset_end_rev_num;
674 [ + - ]: 12 : if (info != NULL) {
675 : 12 : ++(info->changeset_count);
676 [ + - ]: 12 : if (start_rev_num >= needed_rev_num)
677 : 12 : info->changed = true;
678 : 12 : }
679 : : } else {
680 : : // The changeset doesn't exist: leave the revision number as it
681 : : // is, and mark for doing a full database copy.
682 : 3 : need_whole_db = true;
683 : 15 : }
684 : : }
685 : : }
686 [ - + ]: 20 : conn.send_message(REPL_REPLY_END_OF_CHANGES, string(), 0.0);
687 : : }
688 : :
689 : : void
690 : 0 : ChertDatabase::modifications_failed(chert_revision_number_t old_revision,
691 : : chert_revision_number_t new_revision,
692 : : const std::string & msg)
693 : : {
694 : : // Modifications failed. Wipe all the modifications from memory.
695 : : try {
696 : : // Discard any buffered changes and reinitialised cached values
697 : : // from the table.
698 : 0 : cancel();
699 : :
700 : : // Reopen tables with old revision number.
701 : 0 : open_tables(old_revision);
702 : :
703 : : // Increase revision numbers to new revision number plus one,
704 : : // writing increased numbers to all tables.
705 : 0 : ++new_revision;
706 : 0 : set_revision_number(new_revision);
707 : 0 : } catch (const Xapian::Error &e) {
708 : : // We can't get the database into a consistent state, so close
709 : : // it to avoid the risk of database corruption.
710 : 0 : ChertDatabase::close();
711 : : throw Xapian::DatabaseError("Modifications failed (" + msg +
712 : : "), and cannot set consistent table "
713 : 0 : "revision numbers: " + e.get_msg());
714 : : }
715 : 0 : }
716 : :
717 : : void
718 : 1178 : ChertDatabase::apply()
719 : : {
720 : : LOGCALL_VOID(DB, "ChertDatabase::apply", NO_ARGS);
721 [ + + ][ + - ]: 1178 : if (!postlist_table.is_modified() &&
[ + - ][ + - ]
[ + + ][ + + ]
[ + - ][ + + ]
722 : : !position_table.is_modified() &&
723 : : !termlist_table.is_modified() &&
724 : : !value_manager.is_modified() &&
725 : : !synonym_table.is_modified() &&
726 : : !spelling_table.is_modified() &&
727 : : !record_table.is_modified()) {
728 : 692 : return;
729 : : }
730 : :
731 : 486 : chert_revision_number_t old_revision = get_revision_number();
732 : 486 : chert_revision_number_t new_revision = get_next_revision_number();
733 : :
734 : : try {
735 : 486 : set_revision_number(new_revision);
736 : 0 : } catch (const Xapian::Error &e) {
737 : 0 : modifications_failed(old_revision, new_revision, e.get_description());
738 : 0 : throw;
739 : 1178 : } catch (...) {
740 : 0 : modifications_failed(old_revision, new_revision, "Unknown error");
741 : 0 : throw;
742 : : }
743 : : }
744 : :
745 : : void
746 : 154 : ChertDatabase::cancel()
747 : : {
748 : : LOGCALL_VOID(DB, "ChertDatabase::cancel", NO_ARGS);
749 : 154 : postlist_table.cancel();
750 : 152 : position_table.cancel();
751 : 152 : termlist_table.cancel();
752 : 152 : value_manager.cancel();
753 : 152 : synonym_table.cancel();
754 : 152 : spelling_table.cancel();
755 : 152 : record_table.cancel();
756 : 152 : }
757 : :
758 : : Xapian::doccount
759 : 224732 : ChertDatabase::get_doccount() const
760 : : {
761 : : LOGCALL(DB, Xapian::doccount, "ChertDatabase::get_doccount", NO_ARGS);
762 : 224732 : RETURN(record_table.get_doccount());
763 : : }
764 : :
765 : : Xapian::docid
766 : 1924 : ChertDatabase::get_lastdocid() const
767 : : {
768 : : LOGCALL(DB, Xapian::docid, "ChertDatabase::get_lastdocid", NO_ARGS);
769 : 1924 : RETURN(stats.get_last_docid());
770 : : }
771 : :
772 : : totlen_t
773 : 72946 : ChertDatabase::get_total_length() const
774 : : {
775 : : LOGCALL(DB, totlen_t, "ChertDatabase::get_total_length", NO_ARGS);
776 : 72946 : RETURN(stats.get_total_doclen());
777 : : }
778 : :
779 : : Xapian::doclength
780 : 1363 : ChertDatabase::get_avlength() const
781 : : {
782 : : LOGCALL(DB, Xapian::doclength, "ChertDatabase::get_avlength", NO_ARGS);
783 : 1363 : Xapian::doccount doccount = record_table.get_doccount();
784 [ + + ]: 1363 : if (doccount == 0) {
785 : : // Avoid dividing by zero when there are no documents.
786 : 289 : RETURN(0);
787 : : }
788 : 1363 : RETURN(double(stats.get_total_doclen()) / doccount);
789 : : }
790 : :
791 : : Xapian::termcount
792 : 19120662 : ChertDatabase::get_doclength(Xapian::docid did) const
793 : : {
794 : : LOGCALL(DB, Xapian::termcount, "ChertDatabase::get_doclength", did);
795 : : Assert(did != 0);
796 : 19120662 : Xapian::Internal::RefCntPtr<const ChertDatabase> ptrtothis(this);
797 : 19168820 : RETURN(postlist_table.get_doclength(did, ptrtothis));
798 : : }
799 : :
800 : : Xapian::doccount
801 : 194371 : ChertDatabase::get_termfreq(const string & term) const
802 : : {
803 : : LOGCALL(DB, Xapian::doccount, "ChertDatabase::get_termfreq", term);
804 : : Assert(!term.empty());
805 : 194371 : RETURN(postlist_table.get_termfreq(term));
806 : : }
807 : :
808 : : Xapian::termcount
809 : 237067 : ChertDatabase::get_collection_freq(const string & term) const
810 : : {
811 : : LOGCALL(DB, Xapian::termcount, "ChertDatabase::get_collection_freq", term);
812 : : Assert(!term.empty());
813 : 237067 : RETURN(postlist_table.get_collection_freq(term));
814 : : }
815 : :
816 : : Xapian::doccount
817 : 192 : ChertDatabase::get_value_freq(Xapian::valueno valno) const
818 : : {
819 : : LOGCALL(DB, Xapian::doccount, "ChertDatabase::get_value_freq", valno);
820 : 192 : RETURN(value_manager.get_value_freq(valno));
821 : : }
822 : :
823 : : std::string
824 : 1048 : ChertDatabase::get_value_lower_bound(Xapian::valueno valno) const
825 : : {
826 : : LOGCALL(DB, std::string, "ChertDatabase::get_value_lower_bound", valno);
827 : 1048 : RETURN(value_manager.get_value_lower_bound(valno));
828 : : }
829 : :
830 : : std::string
831 : 975 : ChertDatabase::get_value_upper_bound(Xapian::valueno valno) const
832 : : {
833 : : LOGCALL(DB, std::string, "ChertDatabase::get_value_upper_bound", valno);
834 : 975 : RETURN(value_manager.get_value_upper_bound(valno));
835 : : }
836 : :
837 : : Xapian::termcount
838 : 356862 : ChertDatabase::get_doclength_lower_bound() const
839 : : {
840 : 356862 : return stats.get_doclength_lower_bound();
841 : : }
842 : :
843 : : Xapian::termcount
844 : 1567 : ChertDatabase::get_doclength_upper_bound() const
845 : : {
846 : 1567 : return stats.get_doclength_upper_bound();
847 : : }
848 : :
849 : : Xapian::termcount
850 : 235919 : ChertDatabase::get_wdf_upper_bound(const string & term) const
851 : : {
852 : 235919 : return min(get_collection_freq(term), stats.get_wdf_upper_bound());
853 : : }
854 : :
855 : : bool
856 : 657 : ChertDatabase::term_exists(const string & term) const
857 : : {
858 : : LOGCALL(DB, bool, "ChertDatabase::term_exists", term);
859 : : Assert(!term.empty());
860 : 657 : return postlist_table.term_exists(term);
861 : : }
862 : :
863 : : bool
864 : 1594 : ChertDatabase::has_positions() const
865 : : {
866 : 1594 : return !position_table.empty();
867 : : }
868 : :
869 : : LeafPostList *
870 : 144250 : ChertDatabase::open_post_list(const string& term) const
871 : : {
872 : : LOGCALL(DB, LeafPostList *, "ChertDatabase::open_post_list", term);
873 : 144250 : Xapian::Internal::RefCntPtr<const ChertDatabase> ptrtothis(this);
874 : :
875 [ + + ]: 144250 : if (term.empty()) {
876 : 289 : Xapian::doccount doccount = get_doccount();
877 [ + + ]: 289 : if (stats.get_last_docid() == doccount) {
878 : 90 : RETURN(new ContiguousAllDocsPostList(ptrtothis, doccount));
879 : : }
880 : 199 : RETURN(new ChertAllDocsPostList(ptrtothis, doccount));
881 : : }
882 : :
883 : 144262 : RETURN(new ChertPostList(ptrtothis, term, true));
884 : : }
885 : :
886 : : ValueList *
887 : 2688 : ChertDatabase::open_value_list(Xapian::valueno slot) const
888 : : {
889 : : LOGCALL(DB, ValueList *, "ChertDatabase::open_value_list", slot);
890 : 2688 : Xapian::Internal::RefCntPtr<const ChertDatabase> ptrtothis(this);
891 : 2688 : RETURN(new ChertValueList(slot, ptrtothis));
892 : : }
893 : :
894 : : TermList *
895 : 34621 : ChertDatabase::open_term_list(Xapian::docid did) const
896 : : {
897 : : LOGCALL(DB, TermList *, "ChertDatabase::open_term_list", did);
898 : : Assert(did != 0);
899 [ - + ]: 34621 : if (!termlist_table.is_open())
900 : 0 : throw Xapian::FeatureUnavailableError("Database has no termlist");
901 : :
902 : 34621 : Xapian::Internal::RefCntPtr<const ChertDatabase> ptrtothis(this);
903 : 43657 : RETURN(new ChertTermList(ptrtothis, did));
904 : : }
905 : :
906 : : Xapian::Document::Internal *
907 : 228886 : ChertDatabase::open_document(Xapian::docid did, bool lazy) const
908 : : {
909 : : LOGCALL(DB, Xapian::Document::Internal *, "ChertDatabase::open_document", did | lazy);
910 : : Assert(did != 0);
911 [ + + ]: 228886 : if (!lazy) {
912 : : // This will throw DocNotFoundError if the document doesn't exist.
913 : 227516 : (void)get_doclength(did);
914 : : }
915 : :
916 : 219840 : Xapian::Internal::RefCntPtr<const Database::Internal> ptrtothis(this);
917 : 219840 : RETURN(new ChertDocument(ptrtothis, did, &value_manager, &record_table));
918 : : }
919 : :
920 : : PositionList *
921 : 727 : ChertDatabase::open_position_list(Xapian::docid did, const string & term) const
922 : : {
923 : : Assert(did != 0);
924 : :
925 : 727 : AutoPtr<ChertPositionList> poslist(new ChertPositionList);
926 : 727 : if (!poslist->read_data(&position_table, did, term)) {
927 : : // As of 1.1.0, we don't check if the did and term exist - we just
928 : : // return an empty positionlist. If the user really needs to know,
929 : : // they can check for themselves.
930 : : }
931 : :
932 : 727 : return poslist.release();
933 : : }
934 : :
935 : : TermList *
936 : 238 : ChertDatabase::open_allterms(const string & prefix) const
937 : : {
938 : : LOGCALL(DB, TermList *, "ChertDatabase::open_allterms", NO_ARGS);
939 : 238 : RETURN(new ChertAllTermsList(Xapian::Internal::RefCntPtr<const ChertDatabase>(this),
940 : : prefix));
941 : : }
942 : :
943 : : TermList *
944 : 204 : ChertDatabase::open_spelling_termlist(const string & word) const
945 : : {
946 : 204 : return spelling_table.open_termlist(word);
947 : : }
948 : :
949 : : TermList *
950 : 5 : ChertDatabase::open_spelling_wordlist() const
951 : : {
952 : 5 : ChertCursor * cursor = spelling_table.cursor_get();
953 [ - + ]: 5 : if (!cursor) return NULL;
954 : : return new ChertSpellingWordsList(Xapian::Internal::RefCntPtr<const ChertDatabase>(this),
955 : 5 : cursor);
956 : : }
957 : :
958 : : Xapian::doccount
959 : 193 : ChertDatabase::get_spelling_frequency(const string & word) const
960 : : {
961 : 193 : return spelling_table.get_word_frequency(word);
962 : : }
963 : :
964 : : TermList *
965 : 30481 : ChertDatabase::open_synonym_termlist(const string & term) const
966 : : {
967 : 30481 : return synonym_table.open_termlist(term);
968 : : }
969 : :
970 : : TermList *
971 : 10125 : ChertDatabase::open_synonym_keylist(const string & prefix) const
972 : : {
973 : 10125 : ChertCursor * cursor = synonym_table.cursor_get();
974 [ + + ]: 10125 : if (!cursor) return NULL;
975 : : return new ChertSynonymTermList(Xapian::Internal::RefCntPtr<const ChertDatabase>(this),
976 : 10125 : cursor, prefix);
977 : : }
978 : :
979 : : string
980 : 69 : ChertDatabase::get_metadata(const string & key) const
981 : : {
982 : : LOGCALL(DB, string, "ChertDatabase::get_metadata", key);
983 : 69 : string btree_key("\x00\xc0", 2);
984 : 69 : btree_key += key;
985 : 69 : string tag;
986 : 69 : (void)postlist_table.get_exact_entry(btree_key, tag);
987 : 69 : RETURN(tag);
988 : : }
989 : :
990 : : TermList *
991 : 24 : ChertDatabase::open_metadata_keylist(const std::string &prefix) const
992 : : {
993 : : LOGCALL(DB, string, "ChertDatabase::open_metadata_keylist", NO_ARGS);
994 : 24 : ChertCursor * cursor = postlist_table.cursor_get();
995 [ - + ]: 24 : if (!cursor) return NULL;
996 : : return new ChertMetadataTermList(Xapian::Internal::RefCntPtr<const ChertDatabase>(this),
997 : 24 : cursor, prefix);
998 : : }
999 : :
1000 : : string
1001 : 29 : ChertDatabase::get_revision_info() const
1002 : : {
1003 : : LOGCALL(DB, string, "ChertDatabase::get_revision_info", NO_ARGS);
1004 : 29 : string buf;
1005 : 29 : pack_uint(buf, get_revision_number());
1006 : 0 : RETURN(buf);
1007 : : }
1008 : :
1009 : : string
1010 : 1427 : ChertDatabase::get_uuid() const
1011 : : {
1012 : : LOGCALL(DB, string, "ChertDatabase::get_uuid", NO_ARGS);
1013 : 1427 : RETURN(version_file.get_uuid_string());
1014 : : }
1015 : :
1016 : : ///////////////////////////////////////////////////////////////////////////
1017 : :
1018 : 681 : ChertWritableDatabase::ChertWritableDatabase(const string &dir, int action,
1019 : : int block_size)
1020 : : : ChertDatabase(dir, action, block_size),
1021 : : freq_deltas(),
1022 : : doclens(),
1023 : : mod_plists(),
1024 : : change_count(0),
1025 : : flush_threshold(0),
1026 : : modify_shortcut_document(NULL),
1027 : 681 : modify_shortcut_docid(0)
1028 : : {
1029 : : LOGCALL_CTOR(DB, "ChertWritableDatabase", dir | action | block_size);
1030 : :
1031 : 677 : const char *p = getenv("XAPIAN_FLUSH_THRESHOLD");
1032 [ - + # # ]: 677 : if (p)
1033 : 0 : flush_threshold = atoi(p);
1034 [ + - ][ # # ]: 677 : if (flush_threshold == 0)
1035 : 677 : flush_threshold = 10000;
1036 : 677 : }
1037 : :
1038 : 677 : ChertWritableDatabase::~ChertWritableDatabase()
1039 : : {
1040 : : LOGCALL_DTOR(DB, "~ChertWritableDatabase");
1041 : 677 : dtor_called();
1042 [ + - ][ # # ]: 677 : }
[ # # ]
1043 : :
1044 : : void
1045 : 1182 : ChertWritableDatabase::commit()
1046 : : {
1047 [ - + ]: 1182 : if (transaction_active())
1048 : 0 : throw Xapian::InvalidOperationError("Can't commit during a transaction");
1049 [ + + ]: 1182 : if (change_count) flush_postlist_changes();
1050 : 1174 : apply();
1051 : 1174 : }
1052 : :
1053 : : void
1054 : 482 : ChertWritableDatabase::flush_postlist_changes() const
1055 : : {
1056 : 482 : postlist_table.merge_changes(mod_plists, doclens, freq_deltas);
1057 : 474 : stats.write(postlist_table);
1058 : :
1059 : 474 : freq_deltas.clear();
1060 : 474 : doclens.clear();
1061 : 474 : mod_plists.clear();
1062 : 474 : change_count = 0;
1063 : 474 : }
1064 : :
1065 : : void
1066 : 6 : ChertWritableDatabase::close()
1067 : : {
1068 : : LOGCALL_VOID(DB, "ChertWritableDatabase::close", NO_ARGS);
1069 [ + + ]: 6 : if (!transaction_active()) {
1070 : 4 : commit();
1071 : : // FIXME: if commit() throws, should we still close?
1072 : : }
1073 : 6 : ChertDatabase::close();
1074 : 6 : }
1075 : :
1076 : : void
1077 : 1178 : ChertWritableDatabase::apply()
1078 : : {
1079 : 1178 : value_manager.set_value_stats(value_stats);
1080 : 1178 : ChertDatabase::apply();
1081 : 1178 : }
1082 : :
1083 : : void
1084 : 798998 : ChertWritableDatabase::add_freq_delta(const string & tname,
1085 : : Xapian::termcount_diff tf_delta,
1086 : : Xapian::termcount_diff cf_delta)
1087 : : {
1088 : 798998 : map<string, pair<termcount_diff, termcount_diff> >::iterator i;
1089 : 798998 : i = freq_deltas.find(tname);
1090 [ + + ]: 798998 : if (i == freq_deltas.end()) {
1091 : 21554 : freq_deltas.insert(make_pair(tname, make_pair(tf_delta, cf_delta)));
1092 : : } else {
1093 : 777444 : i->second.first += tf_delta;
1094 : 777444 : i->second.second += cf_delta;
1095 : : }
1096 : 798998 : }
1097 : :
1098 : : void
1099 : 776549 : ChertWritableDatabase::insert_mod_plist(Xapian::docid did,
1100 : : const string & tname,
1101 : : Xapian::termcount wdf)
1102 : : {
1103 : : // Find or make the appropriate entry in mod_plists.
1104 : 776549 : map<string, map<docid, pair<char, termcount> > >::iterator j;
1105 : 776549 : j = mod_plists.find(tname);
1106 [ + + ]: 776549 : if (j == mod_plists.end()) {
1107 : 21382 : map<docid, pair<char, termcount> > m;
1108 : 21382 : j = mod_plists.insert(make_pair(tname, m)).first;
1109 : : }
1110 : 776549 : j->second[did] = make_pair('A', wdf);
1111 : 776549 : }
1112 : :
1113 : : void
1114 : 22449 : ChertWritableDatabase::update_mod_plist(Xapian::docid did,
1115 : : const string & tname,
1116 : : char type,
1117 : : Xapian::termcount wdf)
1118 : : {
1119 : : // Find or make the appropriate entry in mod_plists.
1120 : 22449 : map<string, map<docid, pair<char, termcount> > >::iterator j;
1121 : 22449 : j = mod_plists.find(tname);
1122 [ + + ]: 22449 : if (j == mod_plists.end()) {
1123 : 172 : map<docid, pair<char, termcount> > m;
1124 : 172 : j = mod_plists.insert(make_pair(tname, m)).first;
1125 : : }
1126 : :
1127 : 22449 : map<docid, pair<char, termcount> >::iterator k;
1128 : 22449 : k = j->second.find(did);
1129 [ + + ]: 22449 : if (k == j->second.end()) {
1130 : 22250 : j->second.insert(make_pair(did, make_pair(type, wdf)));
1131 : : } else {
1132 [ + + ]: 199 : if (type == 'A') {
1133 : : // Adding an entry which has already been deleted.
1134 : : Assert(k->second.first == 'D');
1135 : 6 : type = 'M';
1136 : : }
1137 : 199 : k->second = make_pair(type, wdf);
1138 : : }
1139 : 22449 : }
1140 : :
1141 : : Xapian::docid
1142 : 68216 : ChertWritableDatabase::add_document(const Xapian::Document & document)
1143 : : {
1144 : : LOGCALL(DB, Xapian::docid, "ChertWritableDatabase::add_document", document);
1145 : : // Make sure the docid counter doesn't overflow.
1146 [ + + ]: 68216 : if (stats.get_last_docid() == Xapian::docid(-1))
1147 : 3 : throw Xapian::DatabaseError("Run out of docids - you'll have to use copydatabase to eliminate any gaps before you can add more documents");
1148 : : // Use the next unused document ID.
1149 : 68213 : RETURN(add_document_(stats.get_next_docid(), document));
1150 : : }
1151 : :
1152 : : Xapian::docid
1153 : 68323 : ChertWritableDatabase::add_document_(Xapian::docid did,
1154 : : const Xapian::Document & document)
1155 : : {
1156 : : LOGCALL(DB, Xapian::docid, "ChertWritableDatabase::add_document_", did | document);
1157 : : Assert(did != 0);
1158 : : try {
1159 : : // Add the record using that document ID.
1160 : 68323 : record_table.replace_record(document.get_data(), did);
1161 : :
1162 : : // Set the values.
1163 : 68323 : value_manager.add_document(did, document, value_stats);
1164 : :
1165 : 68323 : chert_doclen_t new_doclen = 0;
1166 : : {
1167 : 68323 : Xapian::TermIterator term = document.termlist_begin();
1168 : 68323 : Xapian::TermIterator term_end = document.termlist_end();
1169 [ + + ]: 845007 : for ( ; term != term_end; ++term) {
1170 : 776684 : termcount wdf = term.get_wdf();
1171 : : // Calculate the new document length
1172 : 776684 : new_doclen += wdf;
1173 : 776684 : stats.check_wdf(wdf);
1174 : :
1175 : 776684 : string tname = *term;
1176 [ + + ]: 776684 : if (tname.size() > MAX_SAFE_TERM_LENGTH)
1177 : 135 : throw Xapian::InvalidArgumentError("Term too long (> "STRINGIZE(MAX_SAFE_TERM_LENGTH)"): " + tname);
1178 : 776549 : add_freq_delta(tname, 1, wdf);
1179 : 776549 : insert_mod_plist(did, tname, wdf);
1180 : :
1181 : 776549 : PositionIterator pos = term.positionlist_begin();
1182 [ + + ]: 776549 : if (pos != term.positionlist_end()) {
1183 : : position_table.set_positionlist(
1184 : : did, tname,
1185 : 754020 : pos, term.positionlist_end(), false);
1186 : : }
1187 : 68458 : }
1188 : : }
1189 : : LOGLINE(DB, "Calculated doclen for new document " << did << " as " << new_doclen);
1190 : :
1191 : : // Set the termlist.
1192 [ + - ]: 68188 : if (termlist_table.is_open())
1193 : 68188 : termlist_table.set_termlist(did, document, new_doclen);
1194 : :
1195 : : // Set the new document length
1196 : : Assert(doclens.find(did) == doclens.end() || doclens[did] == static_cast<Xapian::termcount>(-1));
1197 : 68188 : doclens[did] = new_doclen;
1198 : 68188 : stats.add_document(new_doclen);
1199 : 270 : } catch (...) {
1200 : : // If an error occurs while adding a document, or doing any other
1201 : : // transaction, the modifications so far must be cleared before
1202 : : // returning control to the user - otherwise partial modifications will
1203 : : // persist in memory, and eventually get written to disk.
1204 : 135 : cancel();
1205 : 135 : throw;
1206 : : }
1207 : :
1208 : : // FIXME: this should be done by checking memory usage, not the number of
1209 : : // changes.
1210 : : // We could also look at:
1211 : : // * mod_plists.size()
1212 : : // * doclens.size()
1213 : : // * freq_deltas.size()
1214 : : //
1215 : : // cout << "+++ mod_plists.size() " << mod_plists.size() <<
1216 : : // ", doclens.size() " << doclens.size() <<
1217 : : // ", freq_deltas.size() " << freq_deltas.size() << endl;
1218 [ + + ]: 68188 : if (++change_count >= flush_threshold) {
1219 : 4 : flush_postlist_changes();
1220 [ + - ]: 4 : if (!transaction_active()) apply();
1221 : : }
1222 : :
1223 : 68188 : RETURN(did);
1224 : : }
1225 : :
1226 : : void
1227 : 9894 : ChertWritableDatabase::delete_document(Xapian::docid did)
1228 : : {
1229 : : LOGCALL_VOID(DB, "ChertWritableDatabase::delete_document", did);
1230 : : Assert(did != 0);
1231 : :
1232 [ - + ]: 9894 : if (!termlist_table.is_open())
1233 : 0 : throw Xapian::FeatureUnavailableError("Database has no termlist");
1234 : :
1235 [ - + ]: 9894 : if (rare(modify_shortcut_docid == did)) {
1236 : : // The modify_shortcut document can't be used for a modification
1237 : : // shortcut now, because it's been deleted!
1238 : 0 : modify_shortcut_document = NULL;
1239 : 0 : modify_shortcut_docid = 0;
1240 : : }
1241 : :
1242 : : // Remove the record. If this fails, just propagate the exception since
1243 : : // the state should still be consistent (most likely it's
1244 : : // DocNotFoundError).
1245 : 9894 : record_table.delete_record(did);
1246 : :
1247 : : try {
1248 : : // Remove the values.
1249 : 9891 : value_manager.delete_document(did, value_stats);
1250 : :
1251 : : // OK, now add entries to remove the postings in the underlying record.
1252 : 9891 : Xapian::Internal::RefCntPtr<const ChertWritableDatabase> ptrtothis(this);
1253 : 9891 : ChertTermList termlist(ptrtothis, did);
1254 : :
1255 : 9891 : stats.delete_document(termlist.get_doclength());
1256 : :
1257 : 9891 : termlist.next();
1258 [ + + ]: 32130 : while (!termlist.at_end()) {
1259 : 22239 : string tname = termlist.get_termname();
1260 : 22239 : position_table.delete_positionlist(did, tname);
1261 : 22239 : termcount wdf = termlist.get_wdf();
1262 : :
1263 : 22239 : add_freq_delta(tname, -1, -wdf);
1264 : 22239 : update_mod_plist(did, tname, 'D', 0u);
1265 : :
1266 : 22239 : termlist.next();
1267 : : }
1268 : :
1269 : : // Remove the termlist.
1270 [ + - ]: 9891 : if (termlist_table.is_open())
1271 : 9891 : termlist_table.delete_termlist(did);
1272 : :
1273 : : // Mark this document as removed.
1274 : 9891 : doclens[did] = static_cast<Xapian::termcount>(-1);
1275 : 0 : } catch (...) {
1276 : : // If an error occurs while deleting a document, or doing any other
1277 : : // transaction, the modifications so far must be cleared before
1278 : : // returning control to the user - otherwise partial modifications will
1279 : : // persist in memory, and eventually get written to disk.
1280 : 0 : cancel();
1281 : 0 : throw;
1282 : : }
1283 : :
1284 [ - + ]: 9891 : if (++change_count >= flush_threshold) {
1285 : 0 : flush_postlist_changes();
1286 [ # # ]: 0 : if (!transaction_active()) apply();
1287 : : }
1288 : 9891 : }
1289 : :
1290 : : void
1291 : 19317 : ChertWritableDatabase::replace_document(Xapian::docid did,
1292 : : const Xapian::Document & document)
1293 : : {
1294 : : LOGCALL_VOID(DB, "ChertWritableDatabase::replace_document", did | document);
1295 : : Assert(did != 0);
1296 : :
1297 : : try {
1298 [ + + ]: 19317 : if (did > stats.get_last_docid()) {
1299 : 92 : stats.set_last_docid(did);
1300 : : // If this docid is above the highwatermark, then we can't be
1301 : : // replacing an existing document.
1302 : 92 : (void)add_document_(did, document);
1303 : 92 : return;
1304 : : }
1305 : :
1306 [ - + ]: 19225 : if (!termlist_table.is_open()) {
1307 : : // We can replace an *unused* docid <= last_docid too.
1308 : 0 : Xapian::Internal::RefCntPtr<const ChertDatabase> ptrtothis(this);
1309 [ # # ]: 0 : if (!postlist_table.document_exists(did, ptrtothis)) {
1310 : 0 : (void)add_document_(did, document);
1311 : : return;
1312 : : }
1313 : 0 : throw Xapian::FeatureUnavailableError("Database has no termlist");
1314 : : }
1315 : :
1316 : : // Check for a document read from this database being replaced - ie, a
1317 : : // modification operation.
1318 : 19225 : bool modifying = false;
1319 [ + + ][ + - ]: 19225 : if (modify_shortcut_docid &&
[ + + ]
1320 : : document.internal->get_docid() == modify_shortcut_docid) {
1321 [ + + ]: 10039 : if (document.internal.get() == modify_shortcut_document) {
1322 : : // We have a docid, it matches, and the pointer matches, so we
1323 : : // can skip modification of any data which hasn't been modified
1324 : : // in the document.
1325 [ + + ]: 10038 : if (!document.internal->modified()) {
1326 : : // If the document is unchanged, we've nothing to do.
1327 : 10002 : return;
1328 : : }
1329 : 36 : modifying = true;
1330 : : LOGLINE(DB, "Detected potential document modification shortcut.");
1331 : : } else {
1332 : : // The modify_shortcut document can't be used for a
1333 : : // modification shortcut now, because it's about to be
1334 : : // modified.
1335 : 1 : modify_shortcut_document = NULL;
1336 : 1 : modify_shortcut_docid = 0;
1337 : : }
1338 : : }
1339 : :
1340 [ + + ][ + + ]: 9223 : if (!modifying || document.internal->terms_modified()) {
[ + + ]
1341 : 9221 : Xapian::Internal::RefCntPtr<const ChertWritableDatabase> ptrtothis(this);
1342 : 9239 : ChertTermList termlist(ptrtothis, did);
1343 : 9203 : Xapian::TermIterator term = document.termlist_begin();
1344 : 9203 : chert_doclen_t old_doclen = termlist.get_doclength();
1345 : 9203 : stats.delete_document(old_doclen);
1346 : 9203 : chert_doclen_t new_doclen = old_doclen;
1347 : :
1348 : 9203 : string old_tname, new_tname;
1349 : :
1350 : 9203 : termlist.next();
1351 [ + + ][ + + ]: 9823 : while (!termlist.at_end() || term != document.termlist_end()) {
[ + + ][ # # ]
[ + + ]
1352 : : int cmp;
1353 [ + + ]: 622 : if (termlist.at_end()) {
1354 : 63 : cmp = 1;
1355 : 63 : new_tname = *term;
1356 : : } else {
1357 : 559 : old_tname = termlist.get_termname();
1358 [ + + ]: 559 : if (term != document.termlist_end()) {
1359 : 550 : new_tname = *term;
1360 : 550 : cmp = old_tname.compare(new_tname);
1361 : : } else {
1362 : 9 : cmp = -1;
1363 : : }
1364 : : }
1365 : :
1366 [ + + ]: 622 : if (cmp < 0) {
1367 : : // Term old_tname has been deleted.
1368 : 69 : termcount old_wdf = termlist.get_wdf();
1369 : 69 : new_doclen -= old_wdf;
1370 : 69 : add_freq_delta(old_tname, -1, -old_wdf);
1371 : 69 : position_table.delete_positionlist(did, old_tname);
1372 : 69 : update_mod_plist(did, old_tname, 'D', 0u);
1373 : 69 : termlist.next();
1374 [ + + ]: 553 : } else if (cmp > 0) {
1375 : : // Term new_tname as been added.
1376 : 107 : termcount new_wdf = term.get_wdf();
1377 : 107 : new_doclen += new_wdf;
1378 : 107 : stats.check_wdf(new_wdf);
1379 [ + + ]: 107 : if (new_tname.size() > MAX_SAFE_TERM_LENGTH)
1380 : 2 : throw Xapian::InvalidArgumentError("Term too long (> "STRINGIZE(MAX_SAFE_TERM_LENGTH)"): " + new_tname);
1381 : 105 : add_freq_delta(new_tname, 1, new_wdf);
1382 : 105 : update_mod_plist(did, new_tname, 'A', new_wdf);
1383 : 105 : PositionIterator pos = term.positionlist_begin();
1384 [ + + ]: 105 : if (pos != term.positionlist_end()) {
1385 : : position_table.set_positionlist(
1386 : : did, new_tname,
1387 : 12 : pos, term.positionlist_end(), false);
1388 : : }
1389 : 105 : ++term;
1390 [ + - ]: 446 : } else if (cmp == 0) {
1391 : : // Term already exists: look for wdf and positionlist changes.
1392 : 446 : termcount old_wdf = termlist.get_wdf();
1393 : 446 : termcount new_wdf = term.get_wdf();
1394 : :
1395 : : // Check the stats even if wdf hasn't changed, because
1396 : : // this is the only document, the stats will have been
1397 : : // zeroed.
1398 : 446 : stats.check_wdf(new_wdf);
1399 : :
1400 [ + + ]: 446 : if (old_wdf != new_wdf) {
1401 : 36 : new_doclen += new_wdf - old_wdf;
1402 : 36 : add_freq_delta(new_tname, 0, new_wdf - old_wdf);
1403 : 36 : update_mod_plist(did, new_tname, 'M', new_wdf);
1404 : : }
1405 : :
1406 : 446 : PositionIterator pos = term.positionlist_begin();
1407 [ + + ]: 446 : if (pos != term.positionlist_end()) {
1408 : : position_table.set_positionlist(did, new_tname, pos,
1409 : : term.positionlist_end(),
1410 : 46 : true);
1411 : : } else {
1412 : 400 : position_table.delete_positionlist(did, new_tname);
1413 : : }
1414 : :
1415 : 446 : ++term;
1416 : 446 : termlist.next();
1417 : : }
1418 : : }
1419 : : LOGLINE(DB, "Calculated doclen for replacement document " << did << " as " << new_doclen);
1420 : :
1421 : : // Set the termlist.
1422 [ + - ]: 9201 : if (termlist_table.is_open())
1423 : 9201 : termlist_table.set_termlist(did, document, new_doclen);
1424 : :
1425 : : // Set the new document length
1426 [ + + ]: 9201 : if (new_doclen != old_doclen)
1427 : 109 : doclens[did] = new_doclen;
1428 : 9229 : stats.add_document(new_doclen);
1429 : : }
1430 : :
1431 [ + + ][ - + ]: 9203 : if (!modifying || document.internal->data_modified()) {
[ + + ]
1432 : : // Replace the record
1433 : 9167 : record_table.replace_record(document.get_data(), did);
1434 : : }
1435 : :
1436 [ + + ][ + + ]: 9203 : if (!modifying || document.internal->values_modified()) {
[ + + ]
1437 : : // Replace the values.
1438 : 9189 : value_manager.replace_document(did, document, value_stats);
1439 : : }
1440 : 36 : } catch (const Xapian::DocNotFoundError &) {
1441 : 18 : (void)add_document_(did, document);
1442 : : return;
1443 : 4 : } catch (...) {
1444 : : // If an error occurs while replacing a document, or doing any other
1445 : : // transaction, the modifications so far must be cleared before
1446 : : // returning control to the user - otherwise partial modifications will
1447 : : // persist in memory, and eventually get written to disk.
1448 : 2 : cancel();
1449 : 2 : throw;
1450 : : }
1451 : :
1452 [ - + ]: 9203 : if (++change_count >= flush_threshold) {
1453 : 0 : flush_postlist_changes();
1454 [ # # ]: 19315 : if (!transaction_active()) apply();
1455 : : }
1456 : : }
1457 : :
1458 : : Xapian::Document::Internal *
1459 : 56011 : ChertWritableDatabase::open_document(Xapian::docid did, bool lazy) const
1460 : : {
1461 : : LOGCALL(DB, Xapian::Document::Internal *, "ChertWritableDatabase::open_document", did | lazy);
1462 : 56011 : modify_shortcut_document = ChertDatabase::open_document(did, lazy);
1463 : : // Store the docid only after open_document() successfully returns, so an
1464 : : // attempt to open a missing document doesn't overwrite this.
1465 : 46981 : modify_shortcut_docid = did;
1466 : 46981 : RETURN(modify_shortcut_document);
1467 : : }
1468 : :
1469 : : Xapian::termcount
1470 : 72454 : ChertWritableDatabase::get_doclength(Xapian::docid did) const
1471 : : {
1472 : : LOGCALL(DB, Xapian::termcount, "ChertWritableDatabase::get_doclength", did);
1473 : 72454 : map<docid, termcount>::const_iterator i = doclens.find(did);
1474 [ + + ]: 72454 : if (i != doclens.end()) {
1475 : 3536 : Xapian::termcount doclen = i->second;
1476 [ + + ]: 3536 : if (doclen == static_cast<Xapian::termcount>(-1)) {
1477 : 6 : throw Xapian::DocNotFoundError("Document " + str(did) + " not found");
1478 : : }
1479 : 3530 : RETURN(doclen);
1480 : : }
1481 : 72448 : RETURN(ChertDatabase::get_doclength(did));
1482 : : }
1483 : :
1484 : : Xapian::doccount
1485 : 2252 : ChertWritableDatabase::get_termfreq(const string & tname) const
1486 : : {
1487 : : LOGCALL(DB, Xapian::doccount, "ChertWritableDatabase::get_termfreq", tname);
1488 : 2252 : Xapian::doccount termfreq = ChertDatabase::get_termfreq(tname);
1489 : 2252 : map<string, pair<termcount_diff, termcount_diff> >::const_iterator i;
1490 : 2252 : i = freq_deltas.find(tname);
1491 [ + + ]: 2252 : if (i != freq_deltas.end()) termfreq += i->second.first;
1492 : 2252 : RETURN(termfreq);
1493 : : }
1494 : :
1495 : : Xapian::termcount
1496 : 497 : ChertWritableDatabase::get_collection_freq(const string & tname) const
1497 : : {
1498 : : LOGCALL(DB, Xapian::termcount, "ChertWritableDatabase::get_collection_freq", tname);
1499 : 497 : Xapian::termcount collfreq = ChertDatabase::get_collection_freq(tname);
1500 : :
1501 : 497 : map<string, pair<termcount_diff, termcount_diff> >::const_iterator i;
1502 : 497 : i = freq_deltas.find(tname);
1503 [ + + ]: 497 : if (i != freq_deltas.end()) collfreq += i->second.second;
1504 : :
1505 : 497 : RETURN(collfreq);
1506 : : }
1507 : :
1508 : : Xapian::doccount
1509 : 112 : ChertWritableDatabase::get_value_freq(Xapian::valueno valno) const
1510 : : {
1511 : : LOGCALL(DB, Xapian::doccount, "ChertWritableDatabase::get_value_freq", valno);
1512 : 112 : map<Xapian::valueno, ValueStats>::const_iterator i;
1513 : 112 : i = value_stats.find(valno);
1514 [ + + ]: 112 : if (i != value_stats.end()) RETURN(i->second.freq);
1515 : 112 : RETURN(ChertDatabase::get_value_freq(valno));
1516 : : }
1517 : :
1518 : : std::string
1519 : 83 : ChertWritableDatabase::get_value_lower_bound(Xapian::valueno valno) const
1520 : : {
1521 : : LOGCALL(DB, std::string, "ChertWritableDatabase::get_value_lower_bound", valno);
1522 : 83 : map<Xapian::valueno, ValueStats>::const_iterator i;
1523 : 83 : i = value_stats.find(valno);
1524 [ + + ]: 83 : if (i != value_stats.end()) RETURN(i->second.lower_bound);
1525 : 83 : RETURN(ChertDatabase::get_value_lower_bound(valno));
1526 : : }
1527 : :
1528 : : std::string
1529 : 112 : ChertWritableDatabase::get_value_upper_bound(Xapian::valueno valno) const
1530 : : {
1531 : : LOGCALL(DB, std::string, "ChertWritableDatabase::get_value_upper_bound", valno);
1532 : 112 : map<Xapian::valueno, ValueStats>::const_iterator i;
1533 : 112 : i = value_stats.find(valno);
1534 [ + + ]: 112 : if (i != value_stats.end()) RETURN(i->second.upper_bound);
1535 : 112 : RETURN(ChertDatabase::get_value_upper_bound(valno));
1536 : : }
1537 : :
1538 : : bool
1539 : 189 : ChertWritableDatabase::term_exists(const string & tname) const
1540 : : {
1541 : : LOGCALL(DB, bool, "ChertWritableDatabase::term_exists", tname);
1542 : 189 : RETURN(get_termfreq(tname) != 0);
1543 : : }
1544 : :
1545 : : LeafPostList *
1546 : 441 : ChertWritableDatabase::open_post_list(const string& tname) const
1547 : : {
1548 : : LOGCALL(DB, LeafPostList *, "ChertWritableDatabase::open_post_list", tname);
1549 : 441 : Xapian::Internal::RefCntPtr<const ChertWritableDatabase> ptrtothis(this);
1550 : :
1551 [ + + ]: 441 : if (tname.empty()) {
1552 : 90 : Xapian::doccount doccount = get_doccount();
1553 [ + + ]: 90 : if (stats.get_last_docid() == doccount) {
1554 : 60 : RETURN(new ContiguousAllDocsPostList(ptrtothis, doccount));
1555 : : }
1556 [ + + ]: 30 : if (doclens.empty()) {
1557 : 6 : RETURN(new ChertAllDocsPostList(ptrtothis, doccount));
1558 : : }
1559 : 24 : RETURN(new ChertAllDocsModifiedPostList(ptrtothis, doccount, doclens));
1560 : : }
1561 : :
1562 : 351 : map<string, map<docid, pair<char, termcount> > >::const_iterator j;
1563 : 351 : j = mod_plists.find(tname);
1564 [ + + ]: 351 : if (j != mod_plists.end()) {
1565 : : // We've got buffered changes to this term's postlist, so we need to
1566 : : // use a ChertModifiedPostList.
1567 : 96 : RETURN(new ChertModifiedPostList(ptrtothis, tname, j->second));
1568 : : }
1569 : :
1570 : 444 : RETURN(new ChertPostList(ptrtothis, tname, true));
1571 : : }
1572 : :
1573 : : ValueList *
1574 : 94 : ChertWritableDatabase::open_value_list(Xapian::valueno slot) const
1575 : : {
1576 : : LOGCALL(DB, ValueList *, "ChertWritableDatabase::open_value_list", slot);
1577 : : // If there are changes, we don't have code to iterate the modified value
1578 : : // list so we need to flush (but don't commit - there may be a transaction
1579 : : // in progress).
1580 [ + + ]: 94 : if (change_count) value_manager.merge_changes();
1581 : 94 : RETURN(ChertDatabase::open_value_list(slot));
1582 : : }
1583 : :
1584 : : TermList *
1585 : 84 : ChertWritableDatabase::open_allterms(const string & prefix) const
1586 : : {
1587 : : LOGCALL(DB, TermList *, "ChertWritableDatabase::open_allterms", NO_ARGS);
1588 : : // If there are changes, terms may have been added or removed, and so we
1589 : : // need to flush (but don't commit - there may be a transaction in
1590 : : // progress).
1591 [ + + ]: 84 : if (change_count) flush_postlist_changes();
1592 : 84 : RETURN(ChertDatabase::open_allterms(prefix));
1593 : : }
1594 : :
1595 : : void
1596 : 154 : ChertWritableDatabase::cancel()
1597 : : {
1598 : 154 : ChertDatabase::cancel();
1599 : 152 : stats.read(postlist_table);
1600 : 152 : freq_deltas.clear();
1601 : 152 : doclens.clear();
1602 : 152 : mod_plists.clear();
1603 : 152 : value_stats.clear();
1604 : 152 : change_count = 0;
1605 : 152 : }
1606 : :
1607 : : void
1608 : 71 : ChertWritableDatabase::add_spelling(const string & word,
1609 : : Xapian::termcount freqinc) const
1610 : : {
1611 : 71 : spelling_table.add_word(word, freqinc);
1612 : 71 : }
1613 : :
1614 : : void
1615 : 32 : ChertWritableDatabase::remove_spelling(const string & word,
1616 : : Xapian::termcount freqdec) const
1617 : : {
1618 : 32 : spelling_table.remove_word(word, freqdec);
1619 : 32 : }
1620 : :
1621 : : TermList *
1622 : 4 : ChertWritableDatabase::open_spelling_wordlist() const
1623 : : {
1624 : 4 : spelling_table.merge_changes();
1625 : 4 : return ChertDatabase::open_spelling_wordlist();
1626 : : }
1627 : :
1628 : : TermList *
1629 : 10122 : ChertWritableDatabase::open_synonym_keylist(const string & prefix) const
1630 : : {
1631 : 10122 : synonym_table.merge_changes();
1632 : 10122 : return ChertDatabase::open_synonym_keylist(prefix);
1633 : : }
1634 : :
1635 : : void
1636 : 107 : ChertWritableDatabase::add_synonym(const string & term,
1637 : : const string & synonym) const
1638 : : {
1639 : 107 : synonym_table.add_synonym(term, synonym);
1640 : 107 : }
1641 : :
1642 : : void
1643 : 1 : ChertWritableDatabase::remove_synonym(const string & term,
1644 : : const string & synonym) const
1645 : : {
1646 : 1 : synonym_table.remove_synonym(term, synonym);
1647 : 1 : }
1648 : :
1649 : : void
1650 : 1 : ChertWritableDatabase::clear_synonyms(const string & term) const
1651 : : {
1652 : 1 : synonym_table.clear_synonyms(term);
1653 : 1 : }
1654 : :
1655 : : void
1656 : 57 : ChertWritableDatabase::set_metadata(const string & key, const string & value)
1657 : : {
1658 : : LOGCALL(DB, string, "ChertWritableDatabase::set_metadata", key | value);
1659 : 57 : string btree_key("\x00\xc0", 2);
1660 : 57 : btree_key += key;
1661 [ + + ]: 57 : if (value.empty()) {
1662 : 9 : postlist_table.del(btree_key);
1663 : : } else {
1664 : 48 : postlist_table.add(btree_key, value);
1665 : 57 : }
1666 : 57 : }
1667 : :
1668 : : void
1669 : 47056 : ChertWritableDatabase::invalidate_doc_object(Xapian::Document::Internal * obj) const
1670 : : {
1671 [ + + ]: 47056 : if (obj == modify_shortcut_document) {
1672 : 36965 : modify_shortcut_document = NULL;
1673 : 36965 : modify_shortcut_docid = 0;
1674 : : }
1675 : 47056 : }
|