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