LCOV - code coverage report
Current view: top level - backends/chert - chert_database.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core r Lines: 623 707 88.1 %
Date: 2011-08-21 Functions: 82 88 93.2 %
Branches: 244 360 67.8 %

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

Generated by: LCOV version 1.8