LCOV - code coverage report
Current view: top level - backends/flint - flint_database.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core r Lines: 599 672 89.1 %
Date: 2011-08-21 Functions: 73 79 92.4 %
Branches: 241 352 68.5 %

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

Generated by: LCOV version 1.8