LCOV - code coverage report
Current view: top level - backends/brass - brass_database.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core r Lines: 572 660 86.7 %
Date: 2011-08-21 Functions: 79 85 92.9 %
Branches: 225 344 65.4 %

           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 : }

Generated by: LCOV version 1.8