LCOV - code coverage report
Current view: top level - backends/glass - glass_database.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core 7028d852e609 Lines: 687 754 91.1 %
Date: 2019-02-17 14:59:59 Functions: 94 96 97.9 %
Branches: 569 1032 55.1 %

           Branch data     Line data    Source code
       1                 :            : /** @file glass_database.cc
       2                 :            :  * @brief glass database
       3                 :            :  */
       4                 :            : /* Copyright 1999,2000,2001 BrightStation PLC
       5                 :            :  * Copyright 2001 Hein Ragas
       6                 :            :  * Copyright 2002 Ananova Ltd
       7                 :            :  * Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2019 Olly Betts
       8                 :            :  * Copyright 2006,2008 Lemur Consulting Ltd
       9                 :            :  * Copyright 2009 Richard Boulton
      10                 :            :  * Copyright 2009 Kan-Ru Chen
      11                 :            :  * Copyright 2011 Dan Colish
      12                 :            :  *
      13                 :            :  * This program is free software; you can redistribute it and/or
      14                 :            :  * modify it under the terms of the GNU General Public License as
      15                 :            :  * published by the Free Software Foundation; either version 2 of the
      16                 :            :  * License, or (at your option) any later version.
      17                 :            :  *
      18                 :            :  * This program is distributed in the hope that it will be useful,
      19                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      20                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      21                 :            :  * GNU General Public License for more details.
      22                 :            :  *
      23                 :            :  * You should have received a copy of the GNU General Public License
      24                 :            :  * along with this program; if not, write to the Free Software
      25                 :            :  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
      26                 :            :  * USA
      27                 :            :  */
      28                 :            : 
      29                 :            : #include <config.h>
      30                 :            : 
      31                 :            : #include "glass_database.h"
      32                 :            : 
      33                 :            : #include "xapian/constants.h"
      34                 :            : #include "xapian/error.h"
      35                 :            : #include "xapian/valueiterator.h"
      36                 :            : 
      37                 :            : #include "backends/contiguousalldocspostlist.h"
      38                 :            : #include "glass_alldocspostlist.h"
      39                 :            : #include "glass_alltermslist.h"
      40                 :            : #include "glass_defs.h"
      41                 :            : #include "glass_docdata.h"
      42                 :            : #include "glass_document.h"
      43                 :            : #include "../flint_lock.h"
      44                 :            : #include "glass_metadata.h"
      45                 :            : #include "glass_positionlist.h"
      46                 :            : #include "glass_postlist.h"
      47                 :            : #include "glass_replicate_internal.h"
      48                 :            : #include "glass_spellingwordslist.h"
      49                 :            : #include "glass_termlist.h"
      50                 :            : #include "glass_valuelist.h"
      51                 :            : #include "glass_values.h"
      52                 :            : #include "debuglog.h"
      53                 :            : #include "fd.h"
      54                 :            : #include "filetests.h"
      55                 :            : #include "io_utils.h"
      56                 :            : #include "pack.h"
      57                 :            : #include "net/remoteconnection.h"
      58                 :            : #include "api/replication.h"
      59                 :            : #include "replicationprotocol.h"
      60                 :            : #include "net/length.h"
      61                 :            : #include "posixy_wrapper.h"
      62                 :            : #include "str.h"
      63                 :            : #include "stringutils.h"
      64                 :            : #include "backends/valuestats.h"
      65                 :            : 
      66                 :            : #include "safesysstat.h"
      67                 :            : #include <sys/types.h>
      68                 :            : 
      69                 :            : #include <algorithm>
      70                 :            : #include <cerrno>
      71                 :            : #include <cstdlib>
      72                 :            : #include <memory>
      73                 :            : #include <string>
      74                 :            : 
      75                 :            : using namespace std;
      76                 :            : using namespace Xapian;
      77                 :            : using Xapian::Internal::intrusive_ptr;
      78                 :            : 
      79                 :            : // The maximum safe term length is determined by the postlist.  There we
      80                 :            : // store the term using pack_string_preserving_sort() which takes the
      81                 :            : // length of the string plus an extra byte (assuming the string doesn't
      82                 :            : // contain any zero bytes), followed by the docid with encoded with
      83                 :            : // pack_uint_preserving_sort() which takes up to 5 bytes (for a 32-bit
      84                 :            : // docid).
      85                 :            : //
      86                 :            : // The Btree manager's key length limit is 255 bytes so the maximum safe term
      87                 :            : // length is 255 - 1 - 5 = 249 bytes.  We actually set the limit at 245 for
      88                 :            : // consistency with flint and chert, and also because this allows for 64-bit
      89                 :            : // docids.
      90                 :            : //
      91                 :            : // If the term contains zero bytes, the limit is lower (by one for each zero
      92                 :            : // byte in the term).
      93                 :            : #define MAX_SAFE_TERM_LENGTH 245
      94                 :            : 
      95                 :            : /* This opens the tables, determining the current and next revision numbers,
      96                 :            :  * and stores handles to the tables.
      97                 :            :  */
      98                 :       3599 : GlassDatabase::GlassDatabase(const string &glass_dir, int flags,
      99                 :            :                              unsigned int block_size)
     100                 :            :         : Xapian::Database::Internal(flags == Xapian::DB_READONLY_ ?
     101                 :            :                                      TRANSACTION_READONLY :
     102                 :            :                                      TRANSACTION_NONE),
     103                 :            :           db_dir(glass_dir),
     104                 :       3599 :           readonly(flags == Xapian::DB_READONLY_),
     105                 :            :           version_file(db_dir),
     106                 :            :           postlist_table(db_dir, readonly),
     107                 :            :           position_table(db_dir, readonly),
     108                 :            :           // Note: (Xapian::DB_READONLY_ & Xapian::DB_NO_TERMLIST) is true,
     109                 :            :           // so opening to read we always permit the termlist to be missing.
     110                 :       3599 :           termlist_table(db_dir, readonly, (flags & Xapian::DB_NO_TERMLIST)),
     111                 :            :           value_manager(&postlist_table, &termlist_table),
     112                 :            :           synonym_table(db_dir, readonly),
     113                 :            :           spelling_table(db_dir, readonly),
     114                 :            :           docdata_table(db_dir, readonly),
     115                 :            :           lock(db_dir),
     116 [ +  + ][ +  - ]:       3618 :           changes(db_dir)
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
     117                 :            : {
     118                 :            :     LOGCALL_CTOR(DB, "GlassDatabase", glass_dir | flags | block_size);
     119                 :            : 
     120         [ +  + ]:       3599 :     if (readonly) {
     121         [ +  + ]:       2145 :         open_tables(flags);
     122                 :       2142 :         return;
     123                 :            :     }
     124                 :            : 
     125                 :            :     // Block size must in the range GLASS_MIN_BLOCKSIZE..GLASS_MAX_BLOCKSIZE
     126                 :            :     // and a power of two.
     127 [ +  + ][ +  + ]:       1454 :     if (block_size < GLASS_MIN_BLOCKSIZE ||
     128         [ +  + ]:        356 :         block_size > GLASS_MAX_BLOCKSIZE ||
     129                 :        356 :         (block_size & (block_size - 1)) != 0) {
     130                 :       1099 :         block_size = GLASS_DEFAULT_BLOCKSIZE;
     131                 :            :     }
     132                 :            : 
     133                 :       1454 :     int action = flags & Xapian::DB_ACTION_MASK_;
     134 [ +  + ][ +  - ]:       1454 :     if (action != Xapian::DB_OPEN && !database_exists()) {
         [ +  + ][ +  + ]
     135                 :            :         // Create the directory for the database, if it doesn't exist
     136                 :            :         // already.
     137         [ +  + ]:        764 :         if (mkdir(db_dir.c_str(), 0755) < 0) {
     138                 :          3 :             int mkdir_errno = errno;
     139 [ +  - ][ +  - ]:          3 :             if (mkdir_errno != EEXIST || !dir_exists(db_dir)) {
                 [ +  - ]
     140         [ +  - ]:          6 :                 throw Xapian::DatabaseCreateError(db_dir + ": mkdir failed",
     141         [ +  - ]:          9 :                                                   mkdir_errno);
     142                 :            :             }
     143                 :            :         }
     144                 :            : 
     145         [ +  - ]:        761 :         get_database_write_lock(flags, true);
     146                 :            : 
     147         [ +  - ]:        761 :         create_and_open_tables(flags, block_size);
     148                 :        761 :         return;
     149                 :            :     }
     150                 :            : 
     151         [ +  + ]:        690 :     if (action == Xapian::DB_CREATE) {
     152         [ +  - ]:          2 :         throw Xapian::DatabaseCreateError("Can't create new database at '" +
     153         [ +  - ]:          2 :                                           db_dir + "': a database already exists and I was told "
     154 [ +  - ][ +  - ]:          3 :                                           "not to overwrite it");
     155                 :            :     }
     156                 :            : 
     157         [ +  + ]:        689 :     get_database_write_lock(flags, false);
     158                 :            :     // if we're overwriting, pretend the db doesn't exist
     159         [ +  + ]:        676 :     if (action == Xapian::DB_CREATE_OR_OVERWRITE) {
     160         [ +  - ]:        165 :         create_and_open_tables(flags, block_size);
     161                 :        165 :         return;
     162                 :            :     }
     163                 :            : 
     164                 :            :     // Open the latest version of each table.
     165         [ +  + ]:       3579 :     open_tables(flags);
     166                 :            : }
     167                 :            : 
     168                 :        305 : GlassDatabase::GlassDatabase(int fd)
     169                 :            :         : Xapian::Database::Internal(TRANSACTION_READONLY),
     170                 :            :           db_dir(),
     171                 :            :           readonly(true),
     172                 :            :           version_file(fd),
     173                 :            :           postlist_table(fd, version_file.get_offset(), readonly),
     174                 :            :           position_table(fd, version_file.get_offset(), readonly),
     175                 :            :           termlist_table(fd, version_file.get_offset(), readonly, true),
     176                 :            :           value_manager(&postlist_table, &termlist_table),
     177                 :            :           synonym_table(fd, version_file.get_offset(), readonly),
     178                 :            :           spelling_table(fd, version_file.get_offset(), readonly),
     179                 :            :           docdata_table(fd, version_file.get_offset(), readonly),
     180                 :            :           lock(),
     181 [ +  - ][ +  - ]:        305 :           changes(string())
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
     182                 :            : {
     183                 :            :     LOGCALL_CTOR(DB, "GlassDatabase", fd);
     184         [ +  - ]:        305 :     open_tables(Xapian::DB_READONLY_);
     185                 :        305 : }
     186                 :            : 
     187                 :      10211 : GlassDatabase::~GlassDatabase()
     188                 :            : {
     189                 :            :     LOGCALL_DTOR(DB, "GlassDatabase");
     190         [ -  + ]:       6329 : }
     191                 :            : 
     192                 :            : bool
     193                 :       1248 : GlassDatabase::database_exists() {
     194                 :            :     LOGCALL(DB, bool, "GlassDatabase::database_exists", NO_ARGS);
     195                 :            :     // The postlist table is the only non-optional one.
     196                 :       1248 :     RETURN(postlist_table.exists());
     197                 :            : }
     198                 :            : 
     199                 :            : void
     200                 :        926 : GlassDatabase::create_and_open_tables(int flags, unsigned int block_size)
     201                 :            : {
     202                 :            :     LOGCALL_VOID(DB, "GlassDatabase::create_and_open_tables", flags|block_size);
     203                 :            :     // The caller is expected to create the database directory if it doesn't
     204                 :            :     // already exist.
     205                 :            : 
     206                 :        926 :     GlassVersion &v = version_file;
     207                 :        926 :     v.create(block_size);
     208                 :            : 
     209                 :        926 :     glass_revision_number_t rev = v.get_revision();
     210                 :        926 :     const string& tmpfile = v.write(rev, flags);
     211                 :            : 
     212         [ +  - ]:        926 :     position_table.create_and_open(flags, v.get_root(Glass::POSITION));
     213         [ +  - ]:        926 :     synonym_table.create_and_open(flags, v.get_root(Glass::SYNONYM));
     214         [ +  - ]:        926 :     spelling_table.create_and_open(flags, v.get_root(Glass::SPELLING));
     215         [ +  - ]:        926 :     docdata_table.create_and_open(flags, v.get_root(Glass::DOCDATA));
     216         [ +  - ]:        926 :     termlist_table.create_and_open(flags, v.get_root(Glass::TERMLIST));
     217         [ +  - ]:        926 :     postlist_table.create_and_open(flags, v.get_root(Glass::POSTLIST));
     218                 :            : 
     219 [ +  - ][ -  + ]:        926 :     if (!v.sync(tmpfile, rev, flags)) {
     220 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseCreateError("Failed to create iamglass file");
                 [ #  # ]
     221                 :            :     }
     222                 :            : 
     223                 :        926 :     Assert(database_exists());
     224                 :        926 : }
     225                 :            : 
     226                 :            : bool
     227                 :       3039 : GlassDatabase::open_tables(int flags)
     228                 :            : {
     229                 :            :     LOGCALL(DB, bool, "GlassDatabase::open_tables", flags);
     230                 :            : 
     231                 :       3039 :     glass_revision_number_t cur_rev = version_file.get_revision();
     232                 :            : 
     233         [ +  + ]:       3039 :     if (cur_rev != 0) {
     234                 :            :         // We're reopening, so ensure that we throw DatabaseError if close()
     235                 :            :         // was called.  It could be argued that reopen() can be a no-op in this
     236                 :            :         // case, but that's confusing if someone expects that reopen() can undo
     237                 :            :         // the effects of close() - in fact reopen() is closer to
     238                 :            :         // update_reader_to_latest_revision().
     239         [ +  + ]:         66 :         if (!postlist_table.is_open())
     240                 :          6 :             GlassTable::throw_database_closed();
     241                 :            :     }
     242                 :            : 
     243                 :       3033 :     version_file.read();
     244                 :       3029 :     glass_revision_number_t rev = version_file.get_revision();
     245 [ +  + ][ +  + ]:       3029 :     if (cur_rev && cur_rev == rev) {
     246                 :            :         // We're reopening a database and the revision hasn't changed so we
     247                 :            :         // don't need to do anything.
     248                 :         39 :         RETURN(false);
     249                 :            :     }
     250                 :            : 
     251                 :       2990 :     docdata_table.open(flags, version_file.get_root(Glass::DOCDATA), rev);
     252                 :       2990 :     spelling_table.open(flags, version_file.get_root(Glass::SPELLING), rev);
     253                 :       2990 :     synonym_table.open(flags, version_file.get_root(Glass::SYNONYM), rev);
     254                 :       2990 :     termlist_table.open(flags, version_file.get_root(Glass::TERMLIST), rev);
     255                 :       2990 :     position_table.open(flags, version_file.get_root(Glass::POSITION), rev);
     256                 :       2990 :     postlist_table.open(flags, version_file.get_root(Glass::POSTLIST), rev);
     257                 :            : 
     258                 :       2990 :     Xapian::termcount swfub = version_file.get_spelling_wordfreq_upper_bound();
     259                 :       2990 :     spelling_table.set_wordfreq_upper_bound(swfub);
     260                 :            : 
     261                 :       2990 :     value_manager.reset();
     262                 :            : 
     263         [ +  + ]:       2990 :     if (!readonly) {
     264                 :        510 :         changes.set_oldest_changeset(version_file.get_oldest_changeset());
     265                 :        510 :         glass_revision_number_t revision = version_file.get_revision();
     266                 :        510 :         GlassChanges * p = changes.start(revision, revision + 1, flags);
     267                 :        510 :         version_file.set_changes(p);
     268                 :        510 :         postlist_table.set_changes(p);
     269                 :        510 :         position_table.set_changes(p);
     270                 :        510 :         termlist_table.set_changes(p);
     271                 :        510 :         synonym_table.set_changes(p);
     272                 :        510 :         spelling_table.set_changes(p);
     273                 :        510 :         docdata_table.set_changes(p);
     274                 :            :     }
     275                 :       2990 :     return true;
     276                 :            : }
     277                 :            : 
     278                 :            : glass_revision_number_t
     279                 :       9075 : GlassDatabase::get_next_revision_number() const
     280                 :            : {
     281                 :            :     LOGCALL(DB, glass_revision_number_t, "GlassDatabase::get_next_revision_number", NO_ARGS);
     282                 :            :     // FIXME: If we permit a revision before the latest and then updating it
     283                 :            :     // (to roll back more recent changes) then we (probably) need this to be
     284                 :            :     // one more than the *highest* revision previously committed.
     285                 :       9075 :     RETURN(version_file.get_revision() + 1);
     286                 :            : }
     287                 :            : 
     288                 :            : void
     289                 :         13 : GlassDatabase::get_changeset_revisions(const string & path,
     290                 :            :                                        glass_revision_number_t * startrev,
     291                 :            :                                        glass_revision_number_t * endrev) const
     292                 :            : {
     293         [ +  - ]:         13 :     FD fd(posixy_open(path.c_str(), O_RDONLY | O_CLOEXEC));
     294         [ -  + ]:         13 :     if (fd < 0) {
     295 [ #  # ][ #  # ]:          0 :         string message = string("Couldn't open changeset ") +
     296         [ #  # ]:          0 :                 path + " to read";
     297         [ #  # ]:          0 :         throw Xapian::DatabaseError(message, errno);
     298                 :            :     }
     299                 :            : 
     300                 :            :     char buf[REASONABLE_CHANGESET_SIZE];
     301                 :         13 :     const char *start = buf;
     302         [ +  - ]:         13 :     const char *end = buf + io_read(fd, buf, REASONABLE_CHANGESET_SIZE);
     303         [ -  + ]:         13 :     if (size_t(end - start) < CONST_STRLEN(CHANGES_MAGIC_STRING))
     304 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseError("Changeset too short at " + path);
                 [ #  # ]
     305         [ -  + ]:         13 :     if (memcmp(start, CHANGES_MAGIC_STRING,
     306                 :         13 :                CONST_STRLEN(CHANGES_MAGIC_STRING)) != 0) {
     307 [ #  # ][ #  # ]:          0 :         string message = string("Changeset at ") +
     308         [ #  # ]:          0 :                 path + " does not contain valid magic string";
     309 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseError(message);
     310                 :            :     }
     311                 :         13 :     start += CONST_STRLEN(CHANGES_MAGIC_STRING);
     312                 :            : 
     313                 :            :     unsigned int changes_version;
     314         [ -  + ]:         13 :     if (!unpack_uint(&start, end, &changes_version))
     315                 :            :         throw Xapian::DatabaseError("Couldn't read a valid version number for "
     316 [ #  # ][ #  # ]:          0 :                                     "changeset at " + path);
                 [ #  # ]
     317         [ -  + ]:         13 :     if (changes_version != CHANGES_VERSION)
     318         [ #  # ]:          0 :         throw Xapian::DatabaseError("Don't support version of changeset at " +
     319 [ #  # ][ #  # ]:          0 :                                     path);
     320                 :            : 
     321         [ -  + ]:         13 :     if (!unpack_uint(&start, end, startrev))
     322                 :            :         throw Xapian::DatabaseError("Couldn't read a valid start revision from "
     323 [ #  # ][ #  # ]:          0 :                                     "changeset at " + path);
                 [ #  # ]
     324                 :            : 
     325         [ -  + ]:         13 :     if (!unpack_uint(&start, end, endrev))
     326                 :            :         throw Xapian::DatabaseError("Couldn't read a valid end revision for "
     327 [ #  # ][ #  # ]:         13 :                                     "changeset at " + path);
                 [ #  # ]
     328                 :         13 : }
     329                 :            : 
     330                 :            : void
     331                 :       9075 : GlassDatabase::set_revision_number(int flags, glass_revision_number_t new_revision)
     332                 :            : {
     333                 :            :     LOGCALL_VOID(DB, "GlassDatabase::set_revision_number", flags|new_revision);
     334                 :            : 
     335                 :       9075 :     glass_revision_number_t rev = version_file.get_revision();
     336 [ -  + ][ #  # ]:       9075 :     if (new_revision <= rev && rev != 0) {
     337         [ #  # ]:          0 :         string m = "New revision ";
     338 [ #  # ][ #  # ]:          0 :         m += str(new_revision);
     339         [ #  # ]:          0 :         m += " <= old revision ";
     340 [ #  # ][ #  # ]:          0 :         m += str(rev);
     341 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseError(m);
     342                 :            :     }
     343                 :            : 
     344                 :       9075 :     value_manager.merge_changes();
     345                 :            : 
     346                 :       9075 :     postlist_table.flush_db();
     347                 :       9073 :     position_table.flush_db();
     348                 :       9073 :     termlist_table.flush_db();
     349                 :       9073 :     synonym_table.flush_db();
     350                 :       9073 :     version_file.set_spelling_wordfreq_upper_bound(spelling_table.flush_db());
     351                 :       9073 :     docdata_table.flush_db();
     352                 :            : 
     353                 :       9073 :     postlist_table.commit(new_revision, version_file.root_to_set(Glass::POSTLIST));
     354                 :       9073 :     position_table.commit(new_revision, version_file.root_to_set(Glass::POSITION));
     355                 :       9073 :     termlist_table.commit(new_revision, version_file.root_to_set(Glass::TERMLIST));
     356                 :       9073 :     synonym_table.commit(new_revision, version_file.root_to_set(Glass::SYNONYM));
     357                 :       9073 :     spelling_table.commit(new_revision, version_file.root_to_set(Glass::SPELLING));
     358                 :       9073 :     docdata_table.commit(new_revision, version_file.root_to_set(Glass::DOCDATA));
     359                 :            : 
     360                 :       9073 :     const string & tmpfile = version_file.write(new_revision, flags);
     361 [ +  - ][ +  - ]:      27219 :     if (!postlist_table.sync() ||
                 [ -  + ]
     362 [ +  - ][ +  - ]:      18146 :         !position_table.sync() ||
     363 [ +  - ][ +  - ]:      18146 :         !termlist_table.sync() ||
     364 [ +  - ][ +  - ]:      18146 :         !synonym_table.sync() ||
     365 [ +  - ][ +  - ]:      18146 :         !spelling_table.sync() ||
     366 [ +  - ][ +  - ]:      27219 :         !docdata_table.sync() ||
                 [ -  + ]
     367         [ +  - ]:       9073 :         !version_file.sync(tmpfile, new_revision, flags)) {
     368                 :          0 :         (void)unlink(tmpfile.c_str());
     369 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseError("Commit failed", errno);
     370                 :            :     }
     371                 :            : 
     372         [ +  - ]:       9073 :     changes.commit(new_revision, flags);
     373                 :       9073 : }
     374                 :            : 
     375                 :            : void
     376                 :         18 : GlassDatabase::request_document(Xapian::docid did) const
     377                 :            : {
     378                 :         18 :     docdata_table.readahead_for_document(did);
     379                 :         18 : }
     380                 :            : 
     381                 :            : void
     382                 :     122697 : GlassDatabase::readahead_for_query(const Xapian::Query &query) const
     383                 :            : {
     384                 :     122697 :     Xapian::TermIterator t;
     385 [ +  - ][ +  - ]:     319165 :     for (t = query.get_unique_terms_begin(); t != Xapian::TermIterator(); ++t) {
         [ +  - ][ +  + ]
     386         [ +  - ]:     206309 :         const string & term = *t;
     387 [ +  - ][ +  - ]:     206309 :         if (!postlist_table.readahead_key(GlassPostListTable::make_key(term)))
                 [ +  + ]
     388         [ +  + ]:     206309 :             break;
     389                 :     319165 :     }
     390                 :     122697 : }
     391                 :            : 
     392                 :            : bool
     393                 :        163 : GlassDatabase::reopen()
     394                 :            : {
     395                 :            :     LOGCALL(DB, bool, "GlassDatabase::reopen", NO_ARGS);
     396         [ +  + ]:        163 :     if (!readonly) RETURN(false);
     397                 :         78 :     RETURN(open_tables(postlist_table.get_flags()));
     398                 :            : }
     399                 :            : 
     400                 :            : void
     401                 :        618 : GlassDatabase::close()
     402                 :            : {
     403                 :            :     LOGCALL_VOID(DB, "GlassDatabase::close", NO_ARGS);
     404                 :        618 :     postlist_table.close(true);
     405                 :        618 :     position_table.close(true);
     406                 :        618 :     termlist_table.close(true);
     407                 :        618 :     synonym_table.close(true);
     408                 :        618 :     spelling_table.close(true);
     409                 :        618 :     docdata_table.close(true);
     410                 :        618 :     lock.release();
     411                 :        618 : }
     412                 :            : 
     413                 :            : void
     414                 :       1450 : GlassDatabase::get_database_write_lock(int flags, bool creating)
     415                 :            : {
     416                 :            :     LOGCALL_VOID(DB, "GlassDatabase::get_database_write_lock", flags|creating);
     417                 :            :     // FIXME: Handle Xapian::DB_DANGEROUS here, perhaps by having readers
     418                 :            :     // get a lock on the revision they're reading, and then requiring the
     419                 :            :     // writer get an exclusive lock in this case.
     420         [ +  - ]:       1450 :     string explanation;
     421                 :       1450 :     bool retry = flags & Xapian::DB_RETRY_LOCK;
     422         [ +  - ]:       1450 :     FlintLock::reason why = lock.lock(true, retry, explanation);
     423         [ +  + ]:       1448 :     if (why != FlintLock::SUCCESS) {
     424 [ +  + ][ +  - ]:         11 :         if (why == FlintLock::UNKNOWN && !creating && !database_exists()) {
         [ +  - ][ +  - ]
                 [ +  + ]
     425         [ +  - ]:          2 :             string msg("No glass database found at path '");
     426         [ +  - ]:          2 :             msg += db_dir;
     427         [ +  - ]:          2 :             msg += '\'';
     428 [ +  - ][ +  - ]:          2 :             throw Xapian::DatabaseNotFoundError(msg);
     429                 :            :         }
     430                 :          9 :         lock.throw_databaselockerror(why, db_dir, explanation);
     431                 :       1448 :     }
     432                 :       1437 : }
     433                 :            : 
     434                 :            : void
     435                 :          9 : GlassDatabase::send_whole_database(RemoteConnection & conn, double end_time)
     436                 :            : {
     437                 :            :     LOGCALL_VOID(DB, "GlassDatabase::send_whole_database", conn | end_time);
     438                 :            : #ifdef XAPIAN_HAS_REMOTE_BACKEND
     439                 :            :     // Send the current revision number in the header.
     440         [ +  - ]:          9 :     string buf;
     441         [ +  - ]:         18 :     string uuid = get_uuid();
     442 [ +  - ][ +  - ]:          9 :     buf += encode_length(uuid.size());
     443         [ +  - ]:          9 :     buf += uuid;
     444 [ +  - ][ +  - ]:          9 :     pack_uint(buf, get_revision());
     445         [ +  - ]:          9 :     conn.send_message(REPL_REPLY_DB_HEADER, buf, end_time);
     446                 :            : 
     447                 :            :     // Send all the tables.  The tables which we want to be cached best after
     448                 :            :     // the copy finishes are sent last.
     449                 :            :     static const char filenames[] =
     450                 :            :         "termlist." GLASS_TABLE_EXTENSION "\0"
     451                 :            :         "synonym." GLASS_TABLE_EXTENSION "\0"
     452                 :            :         "spelling." GLASS_TABLE_EXTENSION "\0"
     453                 :            :         "docdata." GLASS_TABLE_EXTENSION "\0"
     454                 :            :         "position." GLASS_TABLE_EXTENSION "\0"
     455                 :            :         "postlist." GLASS_TABLE_EXTENSION "\0"
     456                 :            :         "iamglass\0";
     457         [ +  - ]:         18 :     string filepath = db_dir;
     458         [ +  - ]:          9 :     filepath += '/';
     459                 :          9 :     const char * p = filenames;
     460         [ +  + ]:         63 :     do {
     461                 :         63 :         size_t len = strlen(p);
     462         [ +  - ]:         63 :         filepath.replace(db_dir.size() + 1, string::npos, p, len);
     463         [ +  - ]:         63 :         FD fd(posixy_open(filepath.c_str(), O_RDONLY | O_CLOEXEC));
     464         [ +  + ]:         63 :         if (fd >= 0) {
     465 [ +  - ][ +  - ]:         43 :             conn.send_message(REPL_REPLY_DB_FILENAME, string(p, len), end_time);
     466         [ +  - ]:         43 :             conn.send_file(REPL_REPLY_DB_FILEDATA, fd, end_time);
     467                 :            :         }
     468                 :         63 :         p += len + 1;
     469                 :          9 :     } while (*p);
     470                 :            : #else
     471                 :            :     (void)conn;
     472                 :            :     (void)end_time;
     473                 :            : #endif
     474                 :          9 : }
     475                 :            : 
     476                 :            : void
     477                 :         23 : GlassDatabase::write_changesets_to_fd(int fd,
     478                 :            :                                       const string & revision,
     479                 :            :                                       bool need_whole_db,
     480                 :            :                                       ReplicationInfo * info)
     481                 :            : {
     482                 :            :     LOGCALL_VOID(DB, "GlassDatabase::write_changesets_to_fd", fd | revision | need_whole_db | info);
     483                 :            : #ifdef XAPIAN_HAS_REMOTE_BACKEND
     484                 :         23 :     int whole_db_copies_left = MAX_DB_COPIES_PER_CONVERSATION;
     485                 :         23 :     glass_revision_number_t start_rev_num = 0;
     486         [ +  - ]:         23 :     string start_uuid = get_uuid();
     487                 :            : 
     488                 :         23 :     glass_revision_number_t needed_rev_num = 0;
     489                 :            : 
     490                 :         23 :     const char * rev_ptr = revision.data();
     491                 :         23 :     const char * rev_end = rev_ptr + revision.size();
     492         [ +  + ]:         23 :     if (!unpack_uint(&rev_ptr, rev_end, &start_rev_num)) {
     493                 :          2 :         need_whole_db = true;
     494                 :            :     }
     495                 :            : 
     496 [ +  - ][ +  - ]:         46 :     RemoteConnection conn(-1, fd, string());
                 [ +  - ]
     497                 :            : 
     498                 :            :     // While the starting revision number is less than the latest revision
     499                 :            :     // number, look for a changeset, and write it.
     500                 :            :     //
     501                 :            :     // FIXME - perhaps we should make hardlinks for all the changesets we're
     502                 :            :     // likely to need, first, and then start sending them, so that there's no
     503                 :            :     // risk of them disappearing while we're sending earlier ones.
     504                 :            :     while (true) {
     505         [ +  + ]:         46 :         if (need_whole_db) {
     506                 :            :             // Decrease the counter of copies left to be sent, and fail
     507                 :            :             // if we've already copied the database enough.  This ensures that
     508                 :            :             // synchronisation attempts always terminate eventually.
     509         [ -  + ]:          9 :             if (whole_db_copies_left == 0) {
     510                 :            :                 conn.send_message(REPL_REPLY_FAIL,
     511                 :            :                                   "Database changing too fast",
     512 [ #  # ][ #  # ]:          0 :                                   0.0);
     513                 :         23 :                 return;
     514                 :            :             }
     515                 :          9 :             whole_db_copies_left--;
     516                 :            : 
     517                 :            :             // Send the whole database across.
     518         [ +  - ]:          9 :             start_rev_num = get_revision();
     519 [ +  - ][ +  - ]:          9 :             start_uuid = get_uuid();
     520                 :            : 
     521         [ +  - ]:          9 :             send_whole_database(conn, 0.0);
     522         [ +  - ]:          9 :             if (info != NULL)
     523                 :          9 :                 ++(info->fullcopy_count);
     524                 :            : 
     525                 :          9 :             need_whole_db = false;
     526                 :            : 
     527         [ +  - ]:          9 :             reopen();
     528 [ +  - ][ +  - ]:          9 :             if (start_uuid == get_uuid()) {
     529                 :            :                 // Send the latest revision number after sending the tables.
     530                 :            :                 // The update must proceed to that revision number before the
     531                 :            :                 // copy is safe to make live.
     532                 :            : 
     533         [ +  - ]:          9 :                 string buf;
     534         [ +  - ]:          9 :                 needed_rev_num = get_revision();
     535         [ +  - ]:          9 :                 pack_uint(buf, needed_rev_num);
     536         [ +  - ]:          9 :                 conn.send_message(REPL_REPLY_DB_FOOTER, buf, 0.0);
     537 [ +  - ][ +  - ]:          9 :                 if (info != NULL && start_rev_num == needed_rev_num)
     538                 :          9 :                     info->changed = true;
     539                 :            :             } else {
     540                 :            :                 // Database has been replaced since we did the copy.  Send a
     541                 :            :                 // higher revision number than the revision we've just copied,
     542                 :            :                 // so that the client doesn't make the copy we've just done
     543                 :            :                 // live, and then mark that we need to do a copy again.
     544                 :            :                 // The client will never actually get the required revision,
     545                 :            :                 // because the next message is going to be the start of a new
     546                 :            :                 // database transfer.
     547                 :            : 
     548         [ #  # ]:          0 :                 string buf;
     549         [ #  # ]:          0 :                 pack_uint(buf, start_rev_num + 1);
     550         [ #  # ]:          0 :                 conn.send_message(REPL_REPLY_DB_FOOTER, buf, 0.0);
     551                 :          9 :                 need_whole_db = true;
     552                 :            :             }
     553                 :            :         } else {
     554                 :            :             // Check if we've sent all the updates.
     555 [ +  - ][ +  + ]:         37 :             if (start_rev_num >= get_revision()) {
     556         [ +  - ]:         23 :                 reopen();
     557 [ +  - ][ -  + ]:         23 :                 if (start_uuid != get_uuid()) {
     558                 :          0 :                     need_whole_db = true;
     559                 :          0 :                     continue;
     560                 :            :                 }
     561 [ +  - ][ +  - ]:         23 :                 if (start_rev_num >= get_revision()) {
     562                 :         23 :                     break;
     563                 :            :                 }
     564                 :            :             }
     565                 :            : 
     566                 :            :             // Look for the changeset for revision start_rev_num.
     567 [ +  - ][ +  - ]:         14 :             string changes_name = db_dir + "/changes" + str(start_rev_num);
                 [ +  - ]
     568         [ +  - ]:         28 :             FD fd_changes(posixy_open(changes_name.c_str(), O_RDONLY | O_CLOEXEC));
     569         [ +  + ]:         14 :             if (fd_changes >= 0) {
     570                 :            :                 // Send it, and also update start_rev_num to the new value
     571                 :            :                 // specified in the changeset.
     572                 :            :                 glass_revision_number_t changeset_start_rev_num;
     573                 :            :                 glass_revision_number_t changeset_end_rev_num;
     574                 :            :                 get_changeset_revisions(changes_name,
     575                 :            :                                         &changeset_start_rev_num,
     576         [ +  - ]:         13 :                                         &changeset_end_rev_num);
     577         [ -  + ]:         13 :                 if (changeset_start_rev_num != start_rev_num) {
     578 [ #  # ][ #  # ]:          0 :                     throw Xapian::DatabaseError("Changeset start revision does not match changeset filename");
                 [ #  # ]
     579                 :            :                 }
     580         [ -  + ]:         13 :                 if (changeset_start_rev_num >= changeset_end_rev_num) {
     581 [ #  # ][ #  # ]:          0 :                     throw Xapian::DatabaseError("Changeset start revision is not less than end revision");
                 [ #  # ]
     582                 :            :                 }
     583                 :            : 
     584         [ +  - ]:         13 :                 conn.send_file(REPL_REPLY_CHANGESET, fd_changes, 0.0);
     585                 :         13 :                 start_rev_num = changeset_end_rev_num;
     586         [ +  - ]:         13 :                 if (info != NULL) {
     587                 :         13 :                     ++(info->changeset_count);
     588         [ +  - ]:         13 :                     if (start_rev_num >= needed_rev_num)
     589                 :         13 :                         info->changed = true;
     590                 :            :                 }
     591                 :            :             } else {
     592                 :            :                 // The changeset doesn't exist: leave the revision number as it
     593                 :            :                 // is, and mark for doing a full database copy.
     594                 :          1 :                 need_whole_db = true;
     595                 :         23 :             }
     596                 :            :         }
     597                 :            :     }
     598 [ +  - ][ +  - ]:         46 :     conn.send_message(REPL_REPLY_END_OF_CHANGES, string(), 0.0);
                 [ +  - ]
     599                 :            : #else
     600                 :            :     (void)fd;
     601                 :            :     (void)revision;
     602                 :            :     (void)need_whole_db;
     603                 :            :     (void)info;
     604                 :            : #endif
     605                 :            : }
     606                 :            : 
     607                 :            : void
     608                 :          2 : GlassDatabase::modifications_failed(glass_revision_number_t new_revision,
     609                 :            :                                     const std::string & msg)
     610                 :            : {
     611                 :            :     // Modifications failed.  Wipe all the modifications from memory.
     612                 :          2 :     int flags = postlist_table.get_flags();
     613                 :          2 :     glass_revision_number_t old_revision = version_file.get_revision();
     614                 :            :     try {
     615                 :            :         // Discard any buffered changes and reinitialised cached values
     616                 :            :         // from the table.
     617         [ -  + ]:          2 :         cancel();
     618                 :            : 
     619                 :            :         // Reopen tables with old revision number.
     620         [ #  # ]:          0 :         version_file.cancel();
     621         [ #  # ]:          0 :         docdata_table.open(flags, version_file.get_root(Glass::DOCDATA), old_revision);
     622         [ #  # ]:          0 :         spelling_table.open(flags, version_file.get_root(Glass::SPELLING), old_revision);
     623         [ #  # ]:          0 :         synonym_table.open(flags, version_file.get_root(Glass::SYNONYM), old_revision);
     624         [ #  # ]:          0 :         termlist_table.open(flags, version_file.get_root(Glass::TERMLIST), old_revision);
     625         [ #  # ]:          0 :         position_table.open(flags, version_file.get_root(Glass::POSITION), old_revision);
     626         [ #  # ]:          0 :         postlist_table.open(flags, version_file.get_root(Glass::POSTLIST), old_revision);
     627                 :            : 
     628                 :          0 :         Xapian::termcount ub = version_file.get_spelling_wordfreq_upper_bound();
     629                 :          0 :         spelling_table.set_wordfreq_upper_bound(ub);
     630                 :            : 
     631                 :          0 :         value_manager.reset();
     632                 :            : 
     633                 :            :         // Increase revision numbers to new revision number plus one,
     634                 :            :         // writing increased numbers to all tables.
     635                 :          0 :         ++new_revision;
     636         [ #  # ]:          0 :         set_revision_number(flags, new_revision);
     637         [ -  + ]:          4 :     } catch (const Xapian::Error &e) {
     638                 :            :         // We failed to roll-back so close the database to avoid the risk of
     639                 :            :         // database corruption.
     640         [ -  + ]:          2 :         GlassDatabase::close();
     641 [ -  + ][ -  + ]:          4 :         throw Xapian::DatabaseError("Modifications failed (" + msg + "), "
     642         [ -  + ]:          4 :                                     "and couldn't open at the old revision: " +
     643 [ -  + ][ -  + ]:          6 :                                     e.get_msg());
     644                 :            :     }
     645                 :            : 
     646                 :            :     GlassChanges * p;
     647                 :          0 :     p = changes.start(old_revision, new_revision, flags);
     648                 :          0 :     version_file.set_changes(p);
     649                 :          0 :     postlist_table.set_changes(p);
     650                 :          0 :     position_table.set_changes(p);
     651                 :          0 :     termlist_table.set_changes(p);
     652                 :          0 :     synonym_table.set_changes(p);
     653                 :          0 :     spelling_table.set_changes(p);
     654                 :          0 :     docdata_table.set_changes(p);
     655                 :          0 : }
     656                 :            : 
     657                 :            : void
     658                 :      13080 : GlassDatabase::apply()
     659                 :            : {
     660                 :            :     LOGCALL_VOID(DB, "GlassDatabase::apply", NO_ARGS);
     661 [ +  - ][ +  + ]:      30218 :     if (!postlist_table.is_modified() &&
     662         [ +  + ]:       8116 :         !position_table.is_modified() &&
     663         [ +  - ]:       8111 :         !termlist_table.is_modified() &&
     664         [ +  + ]:       8106 :         !value_manager.is_modified() &&
     665         [ +  + ]:       8086 :         !synonym_table.is_modified() &&
     666 [ +  + ][ +  - ]:      21171 :         !spelling_table.is_modified() &&
     667                 :       4005 :         !docdata_table.is_modified()) {
     668                 :      13078 :         return;
     669                 :            :     }
     670                 :            : 
     671                 :       9075 :     glass_revision_number_t new_revision = get_next_revision_number();
     672                 :            : 
     673                 :       9075 :     int flags = postlist_table.get_flags();
     674                 :            :     try {
     675         [ +  + ]:       9075 :         set_revision_number(flags, new_revision);
     676                 :          4 :     } catch (const Xapian::Error &e) {
     677 [ -  + ][ +  - ]:          2 :         modifications_failed(new_revision, e.get_description());
     678                 :          0 :         throw;
     679         [ +  - ]:          2 :     } catch (...) {
     680   [ #  #  #  # ]:          0 :         modifications_failed(new_revision, "Unknown error");
     681                 :          0 :         throw;
     682                 :            :     }
     683                 :            : 
     684                 :            :     GlassChanges * p;
     685                 :       9073 :     p = changes.start(new_revision, new_revision + 1, flags);
     686                 :       9073 :     version_file.set_changes(p);
     687                 :       9073 :     postlist_table.set_changes(p);
     688                 :       9073 :     position_table.set_changes(p);
     689                 :       9073 :     termlist_table.set_changes(p);
     690                 :       9073 :     synonym_table.set_changes(p);
     691                 :       9073 :     spelling_table.set_changes(p);
     692                 :       9073 :     docdata_table.set_changes(p);
     693                 :            : }
     694                 :            : 
     695                 :            : void
     696                 :        261 : GlassDatabase::cancel()
     697                 :            : {
     698                 :            :     LOGCALL_VOID(DB, "GlassDatabase::cancel", NO_ARGS);
     699                 :        261 :     version_file.cancel();
     700                 :        261 :     glass_revision_number_t rev = version_file.get_revision();
     701                 :        261 :     postlist_table.cancel(version_file.get_root(Glass::POSTLIST), rev);
     702                 :        247 :     position_table.cancel(version_file.get_root(Glass::POSITION), rev);
     703                 :        247 :     termlist_table.cancel(version_file.get_root(Glass::TERMLIST), rev);
     704                 :        247 :     value_manager.cancel();
     705                 :        247 :     synonym_table.cancel(version_file.get_root(Glass::SYNONYM), rev);
     706                 :        247 :     spelling_table.cancel(version_file.get_root(Glass::SPELLING), rev);
     707                 :        247 :     docdata_table.cancel(version_file.get_root(Glass::DOCDATA), rev);
     708                 :            : 
     709                 :        247 :     Xapian::termcount ub = version_file.get_spelling_wordfreq_upper_bound();
     710                 :        247 :     spelling_table.set_wordfreq_upper_bound(ub);
     711                 :        247 : }
     712                 :            : 
     713                 :            : Xapian::doccount
     714                 :     503132 : GlassDatabase::get_doccount() const
     715                 :            : {
     716                 :            :     LOGCALL(DB, Xapian::doccount, "GlassDatabase::get_doccount", NO_ARGS);
     717                 :     503132 :     RETURN(version_file.get_doccount());
     718                 :            : }
     719                 :            : 
     720                 :            : Xapian::docid
     721                 :      45760 : GlassDatabase::get_lastdocid() const
     722                 :            : {
     723                 :            :     LOGCALL(DB, Xapian::docid, "GlassDatabase::get_lastdocid", NO_ARGS);
     724                 :      45760 :     RETURN(version_file.get_last_docid());
     725                 :            : }
     726                 :            : 
     727                 :            : Xapian::totallength
     728                 :     248465 : GlassDatabase::get_total_length() const
     729                 :            : {
     730                 :            :     LOGCALL(DB, Xapian::totallength, "GlassDatabase::get_total_length", NO_ARGS);
     731                 :     248465 :     RETURN(version_file.get_total_doclen());
     732                 :            : }
     733                 :            : 
     734                 :            : Xapian::termcount
     735                 :   28880171 : GlassDatabase::get_doclength(Xapian::docid did) const
     736                 :            : {
     737                 :            :     LOGCALL(DB, Xapian::termcount, "GlassDatabase::get_doclength", did);
     738                 :            :     Assert(did != 0);
     739                 :   28880171 :     intrusive_ptr<const GlassDatabase> ptrtothis(this);
     740         [ +  + ]:   28880171 :     RETURN(postlist_table.get_doclength(did, ptrtothis));
     741                 :            : }
     742                 :            : 
     743                 :            : Xapian::termcount
     744                 :      33788 : GlassDatabase::get_unique_terms(Xapian::docid did) const
     745                 :            : {
     746                 :            :     LOGCALL(DB, Xapian::termcount, "GlassDatabase::get_unique_terms", did);
     747                 :            :     Assert(did != 0);
     748                 :      33788 :     intrusive_ptr<const GlassDatabase> ptrtothis(this);
     749 [ +  + ][ +  - ]:      33788 :     RETURN(GlassTermList(ptrtothis, did).get_unique_terms());
     750                 :            : }
     751                 :            : 
     752                 :            : void
     753                 :     336387 : GlassDatabase::get_freqs(const string & term,
     754                 :            :                          Xapian::doccount * termfreq_ptr,
     755                 :            :                          Xapian::termcount * collfreq_ptr) const
     756                 :            : {
     757                 :            :     LOGCALL_VOID(DB, "GlassDatabase::get_freqs", term | termfreq_ptr | collfreq_ptr);
     758                 :            :     Assert(!term.empty());
     759                 :     336387 :     postlist_table.get_freqs(term, termfreq_ptr, collfreq_ptr);
     760                 :     336374 : }
     761                 :            : 
     762                 :            : Xapian::doccount
     763                 :       1637 : GlassDatabase::get_value_freq(Xapian::valueno slot) const
     764                 :            : {
     765                 :            :     LOGCALL(DB, Xapian::doccount, "GlassDatabase::get_value_freq", slot);
     766                 :       1637 :     RETURN(value_manager.get_value_freq(slot));
     767                 :            : }
     768                 :            : 
     769                 :            : std::string
     770                 :       2391 : GlassDatabase::get_value_lower_bound(Xapian::valueno slot) const
     771                 :            : {
     772                 :            :     LOGCALL(DB, std::string, "GlassDatabase::get_value_lower_bound", slot);
     773                 :       2391 :     RETURN(value_manager.get_value_lower_bound(slot));
     774                 :            : }
     775                 :            : 
     776                 :            : std::string
     777                 :       2483 : GlassDatabase::get_value_upper_bound(Xapian::valueno slot) const
     778                 :            : {
     779                 :            :     LOGCALL(DB, std::string, "GlassDatabase::get_value_upper_bound", slot);
     780                 :       2483 :     RETURN(value_manager.get_value_upper_bound(slot));
     781                 :            : }
     782                 :            : 
     783                 :            : Xapian::termcount
     784                 :     490688 : GlassDatabase::get_doclength_lower_bound() const
     785                 :            : {
     786                 :     490688 :     return version_file.get_doclength_lower_bound();
     787                 :            : }
     788                 :            : 
     789                 :            : Xapian::termcount
     790                 :       4376 : GlassDatabase::get_doclength_upper_bound() const
     791                 :            : {
     792                 :       4376 :     return version_file.get_doclength_upper_bound();
     793                 :            : }
     794                 :            : 
     795                 :            : Xapian::termcount
     796                 :     312180 : GlassDatabase::get_wdf_upper_bound(const string & term) const
     797                 :            : {
     798                 :            :     Assert(!term.empty());
     799                 :            :     Xapian::termcount wdfub;
     800         [ +  + ]:     312180 :     postlist_table.get_freqs(term, NULL, NULL, &wdfub);
     801                 :     312177 :     return min(wdfub, version_file.get_wdf_upper_bound());
     802                 :            : }
     803                 :            : 
     804                 :            : Xapian::termcount
     805                 :          0 : GlassDatabase::get_unique_terms_lower_bound() const
     806                 :            : {
     807                 :          0 :     return version_file.get_unique_terms_lower_bound();
     808                 :            : }
     809                 :            : 
     810                 :            : bool
     811                 :       2501 : GlassDatabase::term_exists(const string & term) const
     812                 :            : {
     813                 :            :     LOGCALL(DB, bool, "GlassDatabase::term_exists", term);
     814         [ +  + ]:       2501 :     if (term.empty()) {
     815                 :         10 :         RETURN(get_doccount() != 0);
     816                 :            :     }
     817                 :       2491 :     RETURN(postlist_table.term_exists(term));
     818                 :            : }
     819                 :            : 
     820                 :            : bool
     821                 :     114498 : GlassDatabase::has_positions() const
     822                 :            : {
     823                 :     114498 :     return !position_table.empty();
     824                 :            : }
     825                 :            : 
     826                 :            : PostList *
     827                 :       3905 : GlassDatabase::open_post_list(const string& term) const
     828                 :            : {
     829                 :            :     LOGCALL(DB, PostList *, "GlassDatabase::open_post_list", term);
     830                 :       3905 :     RETURN(GlassDatabase::open_leaf_post_list(term, false));
     831                 :            : }
     832                 :            : 
     833                 :            : LeafPostList*
     834                 :     116017 : GlassDatabase::open_leaf_post_list(const string& term, bool need_read_pos) const
     835                 :            : {
     836                 :            :     LOGCALL(DB, LeafPostList *, "GlassDatabase::open_leaf_post_list", term | need_read_pos);
     837                 :            :     (void)need_read_pos;
     838                 :     116017 :     intrusive_ptr<const GlassDatabase> ptrtothis(this);
     839                 :            : 
     840         [ +  + ]:     116017 :     if (term.empty()) {
     841                 :            :         Assert(!need_read_pos);
     842         [ +  - ]:        274 :         Xapian::doccount doccount = get_doccount();
     843         [ +  + ]:        274 :         if (version_file.get_last_docid() == doccount) {
     844 [ +  - ][ +  - ]:        271 :             RETURN(new ContiguousAllDocsPostList(doccount));
     845                 :            :         }
     846 [ +  - ][ +  - ]:          3 :         RETURN(new GlassAllDocsPostList(ptrtothis, doccount));
     847                 :            :     }
     848                 :            : 
     849 [ +  - ][ +  + ]:     116017 :     RETURN(new GlassPostList(ptrtothis, term, true));
     850                 :            : }
     851                 :            : 
     852                 :            : ValueList *
     853                 :      27522 : GlassDatabase::open_value_list(Xapian::valueno slot) const
     854                 :            : {
     855                 :            :     LOGCALL(DB, ValueList *, "GlassDatabase::open_value_list", slot);
     856                 :      27522 :     intrusive_ptr<const GlassDatabase> ptrtothis(this);
     857 [ +  - ][ +  - ]:      27522 :     RETURN(new GlassValueList(slot, ptrtothis));
     858                 :            : }
     859                 :            : 
     860                 :            : TermList *
     861                 :      75007 : GlassDatabase::open_term_list(Xapian::docid did) const
     862                 :            : {
     863                 :            :     LOGCALL(DB, TermList *, "GlassDatabase::open_term_list", did);
     864                 :            :     Assert(did != 0);
     865         [ +  + ]:      75007 :     if (!termlist_table.is_open())
     866                 :          4 :         throw_termlist_table_close_exception();
     867                 :      75003 :     intrusive_ptr<const GlassDatabase> ptrtothis(this);
     868 [ +  - ][ +  + ]:      75003 :     RETURN(new GlassTermList(ptrtothis, did));
     869                 :            : }
     870                 :            : 
     871                 :            : TermList *
     872                 :         92 : GlassDatabase::open_term_list_direct(Xapian::docid did) const
     873                 :            : {
     874                 :         92 :     return GlassDatabase::open_term_list(did);
     875                 :            : }
     876                 :            : 
     877                 :            : Xapian::Document::Internal *
     878                 :     489013 : GlassDatabase::open_document(Xapian::docid did, bool lazy) const
     879                 :            : {
     880                 :            :     LOGCALL(DB, Xapian::Document::Internal *, "GlassDatabase::open_document", did | lazy);
     881                 :            :     Assert(did != 0);
     882         [ +  + ]:     489013 :     if (!lazy) {
     883                 :            :         // This will throw DocNotFoundError if the document doesn't exist.
     884         [ +  + ]:     405094 :         (void)get_doclength(did);
     885                 :            :     }
     886                 :            : 
     887                 :     476946 :     intrusive_ptr<const Database::Internal> ptrtothis(this);
     888 [ +  - ][ +  - ]:     476946 :     RETURN(new GlassDocument(ptrtothis, did, &value_manager, &docdata_table));
     889                 :            : }
     890                 :            : 
     891                 :            : void
     892                 :       3192 : GlassDatabase::read_position_list(GlassRePositionList* pos_list,
     893                 :            :                                   Xapian::docid did,
     894                 :            :                                   const string& term) const
     895                 :            : {
     896                 :            :     Assert(did != 0);
     897                 :       3192 :     pos_list->read_data(did, term);
     898                 :       3192 : }
     899                 :            : 
     900                 :            : Xapian::termcount
     901                 :      24315 : GlassDatabase::positionlist_count(Xapian::docid did, const string& term) const
     902                 :            : {
     903                 :      24315 :     return position_table.positionlist_count(did, term);
     904                 :            : }
     905                 :            : 
     906                 :            : PositionList *
     907                 :      73390 : GlassDatabase::open_position_list(Xapian::docid did, const string& term) const
     908                 :            : {
     909                 :            :     Assert(did != 0);
     910         [ +  + ]:      73390 :     return new GlassPositionList(&position_table, did, term);
     911                 :            : }
     912                 :            : 
     913                 :            : TermList *
     914                 :        538 : GlassDatabase::open_allterms(const string & prefix) const
     915                 :            : {
     916                 :            :     LOGCALL(DB, TermList *, "GlassDatabase::open_allterms", NO_ARGS);
     917 [ +  - ][ +  - ]:        538 :     RETURN(new GlassAllTermsList(intrusive_ptr<const GlassDatabase>(this),
     918                 :            :                                  prefix));
     919                 :            : }
     920                 :            : 
     921                 :            : TermList *
     922                 :        178 : GlassDatabase::open_spelling_termlist(const string & word) const
     923                 :            : {
     924                 :        178 :     return spelling_table.open_termlist(word);
     925                 :            : }
     926                 :            : 
     927                 :            : TermList *
     928                 :          9 : GlassDatabase::open_spelling_wordlist() const
     929                 :            : {
     930                 :          9 :     GlassCursor * cursor = spelling_table.cursor_get();
     931         [ -  + ]:          8 :     if (!cursor) return NULL;
     932                 :            :     return new GlassSpellingWordsList(intrusive_ptr<const GlassDatabase>(this),
     933 [ +  - ][ +  - ]:          8 :                                       cursor);
     934                 :            : }
     935                 :            : 
     936                 :            : Xapian::doccount
     937                 :        171 : GlassDatabase::get_spelling_frequency(const string & word) const
     938                 :            : {
     939                 :        171 :     return spelling_table.get_word_frequency(word);
     940                 :            : }
     941                 :            : 
     942                 :            : TermList *
     943                 :     103032 : GlassDatabase::open_synonym_termlist(const string & term) const
     944                 :            : {
     945                 :     103032 :     return synonym_table.open_termlist(term);
     946                 :            : }
     947                 :            : 
     948                 :            : TermList *
     949                 :      30887 : GlassDatabase::open_synonym_keylist(const string & prefix) const
     950                 :            : {
     951                 :      30887 :     GlassCursor * cursor = synonym_table.cursor_get();
     952         [ +  + ]:      30887 :     if (!cursor) return NULL;
     953                 :            :     return new GlassSynonymTermList(intrusive_ptr<const GlassDatabase>(this),
     954 [ +  - ][ +  - ]:      30887 :                                     cursor, prefix);
     955                 :            : }
     956                 :            : 
     957                 :            : string
     958                 :         96 : GlassDatabase::get_metadata(const string & key) const
     959                 :            : {
     960                 :            :     LOGCALL(DB, string, "GlassDatabase::get_metadata", key);
     961         [ +  - ]:        100 :     string btree_key("\x00\xc0", 2);
     962         [ +  - ]:         96 :     btree_key += key;
     963         [ +  - ]:         96 :     string tag;
     964         [ +  + ]:         96 :     (void)postlist_table.get_exact_entry(btree_key, tag);
     965                 :         96 :     RETURN(tag);
     966                 :            : }
     967                 :            : 
     968                 :            : TermList *
     969                 :         34 : GlassDatabase::open_metadata_keylist(const std::string &prefix) const
     970                 :            : {
     971                 :            :     LOGCALL(DB, TermList *, "GlassDatabase::open_metadata_keylist", NO_ARGS);
     972                 :         34 :     GlassCursor * cursor = postlist_table.cursor_get();
     973         [ -  + ]:         32 :     if (!cursor) RETURN(NULL);
     974 [ +  - ][ +  - ]:         32 :     RETURN(new GlassMetadataTermList(intrusive_ptr<const GlassDatabase>(this),
     975                 :            :                                      cursor, prefix));
     976                 :            : }
     977                 :            : 
     978                 :            : Xapian::rev
     979                 :        114 : GlassDatabase::get_revision() const
     980                 :            : {
     981                 :            :     LOGCALL(DB, Xapian::rev, "GlassDatabase::get_revision", NO_ARGS);
     982                 :        114 :     RETURN(version_file.get_revision());
     983                 :            : }
     984                 :            : 
     985                 :            : string
     986                 :       1309 : GlassDatabase::get_uuid() const
     987                 :            : {
     988                 :            :     LOGCALL(DB, string, "GlassDatabase::get_uuid", NO_ARGS);
     989                 :       1309 :     RETURN(version_file.get_uuid_string());
     990                 :            : }
     991                 :            : 
     992                 :            : void
     993                 :          6 : GlassDatabase::throw_termlist_table_close_exception() const
     994                 :            : {
     995                 :            :     // Either the database has been closed, or else there's no termlist table.
     996                 :            :     // Check if the postlist table is open to determine which is the case.
     997         [ +  + ]:          6 :     if (!postlist_table.is_open())
     998                 :          5 :         GlassTable::throw_database_closed();
     999 [ +  - ][ +  - ]:          1 :     throw Xapian::FeatureUnavailableError("Database has no termlist");
                 [ +  - ]
    1000                 :            : }
    1001                 :            : 
    1002                 :            : void
    1003                 :        405 : GlassDatabase::get_used_docid_range(Xapian::docid & first,
    1004                 :            :                                     Xapian::docid & last) const
    1005                 :            : {
    1006                 :        405 :     last = version_file.get_last_docid();
    1007         [ +  + ]:        405 :     if (last == version_file.get_doccount()) {
    1008                 :            :         // Contiguous range starting at 1.
    1009                 :        372 :         first = 1;
    1010                 :        372 :         return;
    1011                 :            :     }
    1012                 :         33 :     postlist_table.get_used_docid_range(first, last);
    1013                 :            : }
    1014                 :            : 
    1015                 :            : bool
    1016                 :        117 : GlassDatabase::has_uncommitted_changes() const
    1017                 :            : {
    1018                 :        117 :     return false;
    1019                 :            : }
    1020                 :            : 
    1021                 :            : bool
    1022                 :         11 : GlassDatabase::locked() const
    1023                 :            : {
    1024                 :         11 :     return lock.test();
    1025                 :            : }
    1026                 :            : 
    1027                 :            : string
    1028                 :          4 : GlassDatabase::get_description() const
    1029                 :            : {
    1030         [ +  - ]:          4 :     string desc = "Glass(";
    1031         [ -  + ]:          4 :     if (!readonly) {
    1032         [ #  # ]:          0 :         desc += "writable, ";
    1033                 :            :     }
    1034         [ +  - ]:          4 :     desc += db_dir;
    1035         [ +  - ]:          4 :     desc += ')';
    1036                 :          4 :     return desc;
    1037                 :            : }
    1038                 :            : 
    1039                 :            : ///////////////////////////////////////////////////////////////////////////
    1040                 :            : 
    1041                 :       1454 : GlassWritableDatabase::GlassWritableDatabase(const string &dir, int flags,
    1042                 :            :                                              int block_size)
    1043                 :            :         : GlassDatabase(dir, flags, block_size),
    1044                 :            :           change_count(0),
    1045                 :            :           flush_threshold(0),
    1046                 :            :           modify_shortcut_document(NULL),
    1047 [ +  - ][ +  - ]:       1454 :           modify_shortcut_docid(0)
    1048                 :            : {
    1049                 :            :     LOGCALL_CTOR(DB, "GlassWritableDatabase", dir | flags | block_size);
    1050                 :            : 
    1051                 :       1436 :     const char *p = getenv("XAPIAN_FLUSH_THRESHOLD");
    1052         [ -  + ]:       1436 :     if (p)
    1053                 :          0 :         flush_threshold = atoi(p);
    1054         [ +  - ]:       1436 :     if (flush_threshold == 0)
    1055                 :       1436 :         flush_threshold = 10000;
    1056                 :       1436 : }
    1057                 :            : 
    1058                 :       4305 : GlassWritableDatabase::~GlassWritableDatabase()
    1059                 :            : {
    1060                 :            :     LOGCALL_DTOR(DB, "GlassWritableDatabase");
    1061                 :       1435 :     dtor_called();
    1062         [ -  + ]:       2870 : }
    1063                 :            : 
    1064                 :            : void
    1065                 :      13086 : GlassWritableDatabase::commit()
    1066                 :            : {
    1067         [ -  + ]:      13086 :     if (transaction_active())
    1068 [ #  # ][ #  # ]:          0 :         throw Xapian::InvalidOperationError("Can't commit during a transaction");
                 [ #  # ]
    1069         [ +  + ]:      13086 :     if (change_count) flush_postlist_changes();
    1070                 :      13076 :     apply();
    1071                 :      13074 : }
    1072                 :            : 
    1073                 :            : void
    1074                 :     134074 : GlassWritableDatabase::check_flush_threshold()
    1075                 :            : {
    1076                 :            :     // FIXME: this should be done by checking memory usage, not the number of
    1077                 :            :     // changes.  We could also look at the amount of data the inverter object
    1078                 :            :     // currently holds.
    1079         [ +  + ]:     134074 :     if (++change_count >= flush_threshold) {
    1080                 :          4 :         flush_postlist_changes();
    1081         [ +  - ]:          4 :         if (!transaction_active()) apply();
    1082                 :            :     }
    1083                 :     134074 : }
    1084                 :            : 
    1085                 :            : void
    1086                 :       9000 : GlassWritableDatabase::flush_postlist_changes() const
    1087                 :            : {
    1088                 :       9000 :     version_file.set_oldest_changeset(changes.get_oldest_changeset());
    1089                 :       9000 :     inverter.flush(postlist_table);
    1090                 :       8990 :     inverter.flush_pos_lists(position_table);
    1091                 :            : 
    1092                 :       8990 :     change_count = 0;
    1093                 :       8990 : }
    1094                 :            : 
    1095                 :            : void
    1096                 :        603 : GlassWritableDatabase::close()
    1097                 :            : {
    1098                 :            :     LOGCALL_VOID(DB, "GlassWritableDatabase::close", NO_ARGS);
    1099         [ +  + ]:        603 :     if (!transaction_active()) {
    1100                 :        601 :         commit();
    1101                 :            :         // FIXME: if commit() throws, should we still close?
    1102                 :            :     }
    1103                 :        603 :     GlassDatabase::close();
    1104                 :        603 : }
    1105                 :            : 
    1106                 :            : void
    1107                 :      13080 : GlassWritableDatabase::apply()
    1108                 :            : {
    1109                 :      13080 :     value_manager.set_value_stats(value_stats);
    1110                 :      13080 :     GlassDatabase::apply();
    1111                 :      13078 : }
    1112                 :            : 
    1113                 :            : Xapian::docid
    1114                 :      87007 : GlassWritableDatabase::add_document(const Xapian::Document & document)
    1115                 :            : {
    1116                 :            :     LOGCALL(DB, Xapian::docid, "GlassWritableDatabase::add_document", document);
    1117                 :            :     // Make sure the docid counter doesn't overflow.
    1118         [ +  + ]:      87007 :     if (version_file.get_last_docid() == GLASS_MAX_DOCID)
    1119 [ +  - ][ +  - ]:          3 :         throw Xapian::DatabaseError("Run out of docids - you'll have to use copydatabase to eliminate any gaps before you can add more documents");
                 [ +  - ]
    1120                 :            :     // Use the next unused document ID.
    1121                 :      87004 :     RETURN(add_document_(version_file.get_next_docid(), document));
    1122                 :            : }
    1123                 :            : 
    1124                 :            : Xapian::docid
    1125                 :     109054 : GlassWritableDatabase::add_document_(Xapian::docid did,
    1126                 :            :                                      const Xapian::Document & document)
    1127                 :            : {
    1128                 :            :     LOGCALL(DB, Xapian::docid, "GlassWritableDatabase::add_document_", did | document);
    1129                 :            :     Assert(did != 0);
    1130                 :            :     try {
    1131                 :            :         // Set the document data.
    1132 [ +  - ][ +  + ]:     109054 :         docdata_table.replace_document_data(did, document.get_data());
    1133                 :            : 
    1134                 :            :         // Set the values.
    1135         [ +  - ]:     109050 :         value_manager.add_document(did, document, value_stats);
    1136                 :            : 
    1137                 :     109050 :         Xapian::termcount new_doclen = 0;
    1138                 :            :         {
    1139         [ +  - ]:     109050 :             Xapian::TermIterator term = document.termlist_begin();
    1140 [ +  - ][ +  + ]:    2089128 :             for ( ; term != document.termlist_end(); ++term) {
    1141         [ +  - ]:    1980258 :                 termcount wdf = term.get_wdf();
    1142                 :            :                 // Calculate the new document length
    1143                 :    1980258 :                 new_doclen += wdf;
    1144                 :    1980258 :                 version_file.check_wdf(wdf);
    1145                 :            : 
    1146         [ +  - ]:    1980258 :                 string tname = *term;
    1147         [ +  + ]:    1980258 :                 if (tname.size() > MAX_SAFE_TERM_LENGTH)
    1148 [ +  - ][ +  - ]:        180 :                     throw Xapian::InvalidArgumentError("Term too long (> " STRINGIZE(MAX_SAFE_TERM_LENGTH) "): " + tname);
                 [ +  - ]
    1149                 :            : 
    1150         [ +  - ]:    1980078 :                 inverter.add_posting(did, tname, wdf);
    1151         [ +  - ]:    1980078 :                 inverter.set_positionlist(position_table, did, tname, term);
    1152                 :    2089128 :             }
    1153                 :            :         }
    1154                 :            :         LOGLINE(DB, "Calculated doclen for new document " << did << " as " << new_doclen);
    1155                 :            : 
    1156                 :            :         // Set the termlist.
    1157         [ +  + ]:     108870 :         if (termlist_table.is_open())
    1158         [ +  - ]:     108869 :             termlist_table.set_termlist(did, document, new_doclen);
    1159                 :            : 
    1160                 :            :         // Set the new document length
    1161         [ +  - ]:     108870 :         inverter.set_doclength(did, new_doclen, true);
    1162         [ +  - ]:     108870 :         version_file.add_document(new_doclen);
    1163                 :        368 :     } catch (...) {
    1164                 :            :         // If an error occurs while adding a document, or doing any other
    1165                 :            :         // transaction, the modifications so far must be cleared before
    1166                 :            :         // returning control to the user - otherwise partial modifications will
    1167                 :            :         // persist in memory, and eventually get written to disk.
    1168         [ +  + ]:        184 :         cancel();
    1169                 :        180 :         throw;
    1170                 :            :     }
    1171                 :            : 
    1172                 :     108870 :     check_flush_threshold();
    1173                 :            : 
    1174                 :     108870 :     RETURN(did);
    1175                 :            : }
    1176                 :            : 
    1177                 :            : void
    1178                 :      12926 : GlassWritableDatabase::delete_document(Xapian::docid did)
    1179                 :            : {
    1180                 :            :     LOGCALL_VOID(DB, "GlassWritableDatabase::delete_document", did);
    1181                 :            :     Assert(did != 0);
    1182                 :            : 
    1183         [ +  + ]:      12926 :     if (!termlist_table.is_open())
    1184                 :          2 :         throw_termlist_table_close_exception();
    1185                 :            : 
    1186                 :            :     // Remove the document data.  If this fails, just propagate the exception since
    1187                 :            :     // the state should still be consistent.
    1188                 :      12924 :     bool doc_really_existed = docdata_table.delete_document_data(did);
    1189                 :            : 
    1190         [ +  + ]:      12924 :     if (rare(modify_shortcut_docid == did)) {
    1191                 :            :         // The modify_shortcut document can't be used for a modification
    1192                 :            :         // shortcut now, because it's been deleted!
    1193                 :          1 :         modify_shortcut_document = NULL;
    1194                 :          1 :         modify_shortcut_docid = 0;
    1195                 :          1 :         doc_really_existed = true;
    1196                 :            :     }
    1197                 :            : 
    1198         [ +  + ]:      12924 :     if (!doc_really_existed) {
    1199                 :            :         // Ensure we throw DocumentNotFound if the document doesn't exist.
    1200                 :      12856 :         (void)get_doclength(did);
    1201                 :            :     }
    1202                 :            : 
    1203                 :            :     try {
    1204                 :            :         // Remove the values.
    1205         [ +  - ]:      12920 :         value_manager.delete_document(did, value_stats);
    1206                 :            : 
    1207                 :            :         // OK, now add entries to remove the postings in the underlying record.
    1208                 :      12920 :         intrusive_ptr<const GlassWritableDatabase> ptrtothis(this);
    1209 [ +  - ][ +  - ]:      25840 :         GlassTermList termlist(ptrtothis, did);
    1210                 :            : 
    1211         [ +  - ]:      12920 :         version_file.delete_document(termlist.get_doclength());
    1212                 :            : 
    1213         [ +  - ]:      12920 :         termlist.next();
    1214 [ +  - ][ +  + ]:      41241 :         while (!termlist.at_end()) {
    1215         [ +  - ]:      28321 :             string tname = termlist.get_termname();
    1216         [ +  - ]:      28321 :             inverter.delete_positionlist(did, tname);
    1217                 :            : 
    1218 [ +  - ][ +  - ]:      28321 :             inverter.remove_posting(did, tname, termlist.get_wdf());
    1219                 :            : 
    1220         [ +  - ]:      28321 :             termlist.next();
    1221                 :      28321 :         }
    1222                 :            : 
    1223                 :            :         // Remove the termlist.
    1224         [ +  - ]:      12920 :         if (termlist_table.is_open())
    1225         [ +  - ]:      12920 :             termlist_table.delete_termlist(did);
    1226                 :            : 
    1227                 :            :         // Mark this document as removed.
    1228         [ +  - ]:      25840 :         inverter.delete_doclength(did);
    1229                 :          0 :     } catch (...) {
    1230                 :            :         // If an error occurs while deleting a document, or doing any other
    1231                 :            :         // transaction, the modifications so far must be cleared before
    1232                 :            :         // returning control to the user - otherwise partial modifications will
    1233                 :            :         // persist in memory, and eventually get written to disk.
    1234         [ #  # ]:          0 :         cancel();
    1235                 :          0 :         throw;
    1236                 :            :     }
    1237                 :            : 
    1238                 :      12920 :     check_flush_threshold();
    1239                 :      12920 : }
    1240                 :            : 
    1241                 :            : void
    1242                 :      54344 : GlassWritableDatabase::replace_document(Xapian::docid did,
    1243                 :            :                                         const Xapian::Document & document)
    1244                 :            : {
    1245                 :            :     LOGCALL_VOID(DB, "GlassWritableDatabase::replace_document", did | document);
    1246                 :            :     Assert(did != 0);
    1247                 :            : 
    1248                 :            :     try {
    1249         [ +  + ]:      54344 :         if (did > version_file.get_last_docid()) {
    1250                 :      22029 :             version_file.set_last_docid(did);
    1251                 :            :             // If this docid is above the highwatermark, then we can't be
    1252                 :            :             // replacing an existing document.
    1253         [ +  + ]:      22029 :             (void)add_document_(did, document);
    1254                 :      21981 :             return;
    1255                 :            :         }
    1256                 :            : 
    1257         [ +  + ]:      32315 :         if (!termlist_table.is_open()) {
    1258                 :            :             // We can replace an *unused* docid <= last_docid too.
    1259                 :          2 :             intrusive_ptr<const GlassDatabase> ptrtothis(this);
    1260 [ -  + ][ #  # ]:          2 :             if (!postlist_table.document_exists(did, ptrtothis)) {
    1261         [ #  # ]:          0 :                 (void)add_document_(did, document);
    1262                 :          0 :                 return;
    1263                 :            :             }
    1264                 :          2 :             throw_termlist_table_close_exception();
    1265                 :            :         }
    1266                 :            : 
    1267                 :            :         // Check for a document read from this database being replaced - ie, a
    1268                 :            :         // modification operation.
    1269                 :      32313 :         bool modifying = false;
    1270   [ +  +  +  - ]:      52393 :         if (modify_shortcut_docid &&
                 [ +  + ]
    1271                 :      20080 :             document.internal->get_docid() == modify_shortcut_docid) {
    1272         [ +  + ]:      20080 :             if (document.internal.get() == modify_shortcut_document) {
    1273                 :            :                 // We have a docid, it matches, and the pointer matches, so we
    1274                 :            :                 // can skip modification of any data which hasn't been modified
    1275                 :            :                 // in the document.
    1276         [ +  + ]:      20078 :                 if (!document.internal->modified()) {
    1277                 :            :                     // If the document is unchanged, we've nothing to do.
    1278                 :      20006 :                     return;
    1279                 :            :                 }
    1280                 :         72 :                 modifying = true;
    1281                 :            :                 LOGLINE(DB, "Detected potential document modification shortcut.");
    1282                 :            :             } else {
    1283                 :            :                 // The modify_shortcut document can't be used for a
    1284                 :            :                 // modification shortcut now, because it's about to be
    1285                 :            :                 // modified.
    1286                 :          2 :                 modify_shortcut_document = NULL;
    1287                 :         74 :                 modify_shortcut_docid = 0;
    1288                 :            :             }
    1289                 :            :         }
    1290                 :            : 
    1291 [ +  + ][ +  + ]:      12307 :         if (!modifying || document.internal->terms_modified()) {
                 [ +  + ]
    1292   [ +  +  +  + ]:      12371 :             bool pos_modified = !modifying ||
    1293                 :         68 :                                 document.internal->positions_modified();
    1294                 :      12303 :             intrusive_ptr<const GlassWritableDatabase> ptrtothis(this);
    1295 [ +  - ][ +  - ]:      24606 :             GlassTermList termlist(ptrtothis, did, false);
                 [ +  + ]
    1296                 :            :             // We passed false for throw_if_not_present so check at_end()
    1297                 :            :             // before next() to see if the document isn't present at all.
    1298 [ +  - ][ +  + ]:      12303 :             if (termlist.at_end()) {
    1299         [ +  - ]:         21 :                 (void)add_document_(did, document);
    1300                 :         21 :                 return;
    1301                 :            :             }
    1302 [ +  - ][ +  + ]:      24585 :             Xapian::TermIterator term = document.termlist_begin();
    1303         [ +  - ]:      12282 :             Xapian::termcount old_doclen = termlist.get_doclength();
    1304                 :      12282 :             version_file.delete_document(old_doclen);
    1305                 :      12282 :             Xapian::termcount new_doclen = old_doclen;
    1306                 :            : 
    1307 [ +  - ][ +  - ]:      24564 :             string old_tname, new_tname;
    1308                 :            : 
    1309         [ +  - ]:      12282 :             termlist.next();
    1310 [ +  - ][ +  + ]:      13146 :             while (!termlist.at_end() || term != document.termlist_end()) {
         [ +  + ][ +  + ]
           [ +  +  #  # ]
    1311                 :            :                 int cmp;
    1312 [ +  - ][ +  + ]:        866 :                 if (termlist.at_end()) {
    1313                 :        112 :                     cmp = 1;
    1314 [ +  - ][ +  - ]:        112 :                     new_tname = *term;
    1315                 :            :                 } else {
    1316 [ +  - ][ +  - ]:        754 :                     old_tname = termlist.get_termname();
    1317         [ +  + ]:        754 :                     if (term != document.termlist_end()) {
    1318 [ +  - ][ +  - ]:        742 :                         new_tname = *term;
    1319         [ +  - ]:        742 :                         cmp = old_tname.compare(new_tname);
    1320                 :            :                     } else {
    1321                 :         12 :                         cmp = -1;
    1322                 :            :                     }
    1323                 :            :                 }
    1324                 :            : 
    1325         [ +  + ]:        866 :                 if (cmp < 0) {
    1326                 :            :                     // Term old_tname has been deleted.
    1327         [ +  - ]:         92 :                     termcount old_wdf = termlist.get_wdf();
    1328                 :         92 :                     new_doclen -= old_wdf;
    1329         [ +  - ]:         92 :                     inverter.remove_posting(did, old_tname, old_wdf);
    1330         [ +  - ]:         92 :                     if (pos_modified)
    1331         [ +  - ]:         92 :                         inverter.delete_positionlist(did, old_tname);
    1332         [ +  - ]:         92 :                     termlist.next();
    1333         [ +  + ]:        774 :                 } else if (cmp > 0) {
    1334                 :            :                     // Term new_tname as been added.
    1335         [ +  - ]:        170 :                     termcount new_wdf = term.get_wdf();
    1336                 :        170 :                     new_doclen += new_wdf;
    1337                 :        170 :                     version_file.check_wdf(new_wdf);
    1338         [ +  + ]:        170 :                     if (new_tname.size() > MAX_SAFE_TERM_LENGTH)
    1339 [ +  - ][ +  - ]:          2 :                         throw Xapian::InvalidArgumentError("Term too long (> " STRINGIZE(MAX_SAFE_TERM_LENGTH) "): " + new_tname);
                 [ +  - ]
    1340         [ +  - ]:        168 :                     inverter.add_posting(did, new_tname, new_wdf);
    1341         [ +  + ]:        168 :                     if (pos_modified) {
    1342         [ +  - ]:         78 :                         inverter.set_positionlist(position_table, did, new_tname, term);
    1343                 :            :                     }
    1344         [ +  - ]:        168 :                     ++term;
    1345         [ +  - ]:        604 :                 } else if (cmp == 0) {
    1346                 :            :                     // Term already exists: look for wdf and positionlist changes.
    1347         [ +  - ]:        604 :                     termcount old_wdf = termlist.get_wdf();
    1348         [ +  - ]:        604 :                     termcount new_wdf = term.get_wdf();
    1349                 :            : 
    1350                 :            :                     // Check the stats even if wdf hasn't changed, because if
    1351                 :            :                     // this is the only document, the stats will have been
    1352                 :            :                     // zeroed.
    1353                 :        604 :                     version_file.check_wdf(new_wdf);
    1354                 :            : 
    1355         [ +  + ]:        604 :                     if (old_wdf != new_wdf) {
    1356                 :         48 :                         new_doclen += new_wdf - old_wdf;
    1357         [ +  - ]:         48 :                         inverter.update_posting(did, new_tname, old_wdf, new_wdf);
    1358                 :            :                     }
    1359                 :            : 
    1360         [ +  + ]:        604 :                     if (pos_modified) {
    1361         [ +  - ]:        560 :                         inverter.set_positionlist(position_table, did, new_tname, term, true);
    1362                 :            :                     }
    1363                 :            : 
    1364         [ +  - ]:        604 :                     ++term;
    1365         [ +  - ]:        604 :                     termlist.next();
    1366                 :            :                 }
    1367                 :            :             }
    1368                 :            :             LOGLINE(DB, "Calculated doclen for replacement document " << did << " as " << new_doclen);
    1369                 :            : 
    1370                 :            :             // Set the termlist.
    1371         [ +  - ]:      12280 :             if (termlist_table.is_open())
    1372         [ +  - ]:      12280 :                 termlist_table.set_termlist(did, document, new_doclen);
    1373                 :            : 
    1374                 :            :             // Set the new document length
    1375         [ +  + ]:      12280 :             if (new_doclen != old_doclen)
    1376         [ +  - ]:        162 :                 inverter.set_doclength(did, new_doclen, false);
    1377         [ +  - ]:      24583 :             version_file.add_document(new_doclen);
    1378                 :            :         }
    1379                 :            : 
    1380 [ +  + ][ -  + ]:      12284 :         if (!modifying || document.internal->data_modified()) {
                 [ +  + ]
    1381                 :            :             // Update the document data.
    1382 [ +  - ][ +  - ]:      12212 :             docdata_table.replace_document_data(did, document.get_data());
    1383                 :            :         }
    1384                 :            : 
    1385 [ +  + ][ +  + ]:      12284 :         if (!modifying || document.internal->values_modified()) {
                 [ +  + ]
    1386                 :            :             // Replace the values.
    1387         [ +  - ]:      12256 :             value_manager.replace_document(did, document, value_stats);
    1388                 :            :         }
    1389                 :        104 :     } catch (...) {
    1390                 :            :         // If an error occurs while replacing a document, or doing any other
    1391                 :            :         // transaction, the modifications so far must be cleared before
    1392                 :            :         // returning control to the user - otherwise partial modifications will
    1393                 :            :         // persist in memory, and eventually get written to disk.
    1394         [ +  + ]:         52 :         cancel();
    1395                 :         47 :         throw;
    1396                 :            :     }
    1397                 :            : 
    1398                 :      54292 :     check_flush_threshold();
    1399                 :            : }
    1400                 :            : 
    1401                 :            : Xapian::Document::Internal *
    1402                 :     240181 : GlassWritableDatabase::open_document(Xapian::docid did, bool lazy) const
    1403                 :            : {
    1404                 :            :     LOGCALL(DB, Xapian::Document::Internal *, "GlassWritableDatabase::open_document", did | lazy);
    1405                 :     240181 :     modify_shortcut_document = GlassDatabase::open_document(did, lazy);
    1406                 :            :     // Store the docid only after open_document() successfully returns, so an
    1407                 :            :     // attempt to open a missing document doesn't overwrite this.
    1408                 :     228141 :     modify_shortcut_docid = did;
    1409                 :     228141 :     RETURN(modify_shortcut_document);
    1410                 :            : }
    1411                 :            : 
    1412                 :            : Xapian::termcount
    1413                 :     187077 : GlassWritableDatabase::get_doclength(Xapian::docid did) const
    1414                 :            : {
    1415                 :            :     LOGCALL(DB, Xapian::termcount, "GlassWritableDatabase::get_doclength", did);
    1416                 :            :     Xapian::termcount doclen;
    1417 [ +  + ][ +  + ]:     187077 :     if (inverter.get_doclength(did, doclen))
    1418                 :      84113 :         RETURN(doclen);
    1419         [ +  + ]:     187069 :     RETURN(GlassDatabase::get_doclength(did));
    1420                 :            : }
    1421                 :            : 
    1422                 :            : Xapian::termcount
    1423                 :      12172 : GlassWritableDatabase::get_unique_terms(Xapian::docid did) const
    1424                 :            : {
    1425                 :            :     LOGCALL(DB, Xapian::termcount, "GlassWritableDatabase::get_unique_terms", did);
    1426                 :            :     Assert(did != 0);
    1427                 :            :     // Note that the "approximate" size should be exact in this case.
    1428                 :            :     //
    1429                 :            :     // get_unique_terms() really ought to only count terms with wdf > 0, but
    1430                 :            :     // that's expensive to calculate on demand, so for now let's just ensure
    1431                 :            :     // unique_terms <= doclen.
    1432                 :            :     Xapian::termcount doclen;
    1433 [ +  - ][ +  + ]:      12172 :     if (inverter.get_doclength(did, doclen)) {
    1434                 :         72 :         intrusive_ptr<const GlassDatabase> ptrtothis(this);
    1435         [ +  - ]:        144 :         GlassTermList termlist(ptrtothis, did);
    1436         [ +  - ]:        144 :         RETURN(min(doclen, termlist.get_approx_size()));
    1437                 :            :     }
    1438         [ +  + ]:      12172 :     RETURN(GlassDatabase::get_unique_terms(did));
    1439                 :            : }
    1440                 :            : 
    1441                 :            : void
    1442                 :       3632 : GlassWritableDatabase::get_freqs(const string & term,
    1443                 :            :                                  Xapian::doccount * termfreq_ptr,
    1444                 :            :                                  Xapian::termcount * collfreq_ptr) const
    1445                 :            : {
    1446                 :            :     LOGCALL_VOID(DB, "GlassWritableDatabase::get_freqs", term | termfreq_ptr | collfreq_ptr);
    1447                 :            :     Assert(!term.empty());
    1448         [ +  - ]:       3632 :     GlassDatabase::get_freqs(term, termfreq_ptr, collfreq_ptr);
    1449                 :            :     Xapian::termcount_diff tf_delta, cf_delta;
    1450 [ +  - ][ +  + ]:       3632 :     if (inverter.get_deltas(term, tf_delta, cf_delta)) {
    1451         [ +  + ]:        420 :         if (termfreq_ptr)
    1452                 :        371 :             *termfreq_ptr += tf_delta;
    1453         [ +  + ]:        420 :         if (collfreq_ptr)
    1454                 :         61 :             *collfreq_ptr += cf_delta;
    1455                 :            :     }
    1456                 :       3632 : }
    1457                 :            : 
    1458                 :            : Xapian::doccount
    1459                 :      19074 : GlassWritableDatabase::get_value_freq(Xapian::valueno slot) const
    1460                 :            : {
    1461                 :            :     LOGCALL(DB, Xapian::doccount, "GlassWritableDatabase::get_value_freq", slot);
    1462                 :      19074 :     map<Xapian::valueno, ValueStats>::const_iterator i;
    1463         [ +  - ]:      19074 :     i = value_stats.find(slot);
    1464         [ +  + ]:      19074 :     if (i != value_stats.end()) RETURN(i->second.freq);
    1465         [ +  - ]:      19074 :     RETURN(GlassDatabase::get_value_freq(slot));
    1466                 :            : }
    1467                 :            : 
    1468                 :            : std::string
    1469                 :      28454 : GlassWritableDatabase::get_value_lower_bound(Xapian::valueno slot) const
    1470                 :            : {
    1471                 :            :     LOGCALL(DB, std::string, "GlassWritableDatabase::get_value_lower_bound", slot);
    1472                 :      28454 :     map<Xapian::valueno, ValueStats>::const_iterator i;
    1473         [ +  - ]:      28454 :     i = value_stats.find(slot);
    1474 [ +  + ][ +  - ]:      28454 :     if (i != value_stats.end()) RETURN(i->second.lower_bound);
    1475         [ +  - ]:      28454 :     RETURN(GlassDatabase::get_value_lower_bound(slot));
    1476                 :            : }
    1477                 :            : 
    1478                 :            : std::string
    1479                 :      28520 : GlassWritableDatabase::get_value_upper_bound(Xapian::valueno slot) const
    1480                 :            : {
    1481                 :            :     LOGCALL(DB, std::string, "GlassWritableDatabase::get_value_upper_bound", slot);
    1482                 :      28520 :     map<Xapian::valueno, ValueStats>::const_iterator i;
    1483         [ +  - ]:      28520 :     i = value_stats.find(slot);
    1484 [ +  + ][ +  - ]:      28520 :     if (i != value_stats.end()) RETURN(i->second.upper_bound);
    1485         [ +  - ]:      28520 :     RETURN(GlassDatabase::get_value_upper_bound(slot));
    1486                 :            : }
    1487                 :            : 
    1488                 :            : bool
    1489                 :        283 : GlassWritableDatabase::term_exists(const string & tname) const
    1490                 :            : {
    1491                 :            :     LOGCALL(DB, bool, "GlassWritableDatabase::term_exists", tname);
    1492         [ +  + ]:        283 :     if (tname.empty()) {
    1493         [ +  - ]:         16 :         RETURN(get_doccount() != 0);
    1494                 :            :     }
    1495                 :            :     Xapian::doccount tf;
    1496         [ +  - ]:        267 :     get_freqs(tname, &tf, NULL);
    1497                 :        283 :     RETURN(tf != 0);
    1498                 :            : }
    1499                 :            : 
    1500                 :            : bool
    1501                 :      13940 : GlassWritableDatabase::has_positions() const
    1502                 :            : {
    1503                 :      13940 :     return inverter.has_positions(position_table);
    1504                 :            : }
    1505                 :            : 
    1506                 :            : PostList *
    1507                 :        751 : GlassWritableDatabase::open_post_list(const string& term) const
    1508                 :            : {
    1509                 :            :     LOGCALL(DB, PostList *, "GlassWritableDatabase::open_post_list", term);
    1510                 :        751 :     RETURN(GlassWritableDatabase::open_leaf_post_list(term, false));
    1511                 :            : }
    1512                 :            : 
    1513                 :            : LeafPostList *
    1514                 :       1071 : GlassWritableDatabase::open_leaf_post_list(const string& term,
    1515                 :            :                                            bool need_read_pos) const
    1516                 :            : {
    1517                 :            :     LOGCALL(DB, LeafPostList *, "GlassWritableDatabase::open_leaf_post_list", term | need_read_pos);
    1518                 :            :     (void)need_read_pos;
    1519                 :       1071 :     intrusive_ptr<const GlassWritableDatabase> ptrtothis(this);
    1520                 :            : 
    1521         [ +  + ]:       1071 :     if (term.empty()) {
    1522                 :            :         Assert(!need_read_pos);
    1523         [ +  - ]:        162 :         Xapian::doccount doccount = get_doccount();
    1524         [ +  + ]:        162 :         if (version_file.get_last_docid() == doccount) {
    1525 [ +  - ][ +  - ]:        116 :             RETURN(new ContiguousAllDocsPostList(doccount));
    1526                 :            :         }
    1527         [ +  - ]:         46 :         inverter.flush_doclengths(postlist_table);
    1528 [ +  - ][ +  - ]:         46 :         RETURN(new GlassAllDocsPostList(ptrtothis, doccount));
                 [ +  - ]
    1529                 :            :     }
    1530                 :            : 
    1531                 :            :     // Flush any buffered changes for this term's postlist so we can just
    1532                 :            :     // iterate from the flushed state.
    1533         [ +  - ]:        909 :     inverter.flush_post_list(postlist_table, term);
    1534 [ +  - ][ +  - ]:       1071 :     RETURN(new GlassPostList(ptrtothis, term, true));
                 [ +  + ]
    1535                 :            : }
    1536                 :            : 
    1537                 :            : ValueList *
    1538                 :       9563 : GlassWritableDatabase::open_value_list(Xapian::valueno slot) const
    1539                 :            : {
    1540                 :            :     LOGCALL(DB, ValueList *, "GlassWritableDatabase::open_value_list", slot);
    1541                 :            :     // If there are changes, we don't have code to iterate the modified value
    1542                 :            :     // list so we need to flush (but don't commit - there may be a transaction
    1543                 :            :     // in progress).
    1544         [ +  + ]:       9563 :     if (change_count) value_manager.merge_changes();
    1545                 :       9563 :     RETURN(GlassDatabase::open_value_list(slot));
    1546                 :            : }
    1547                 :            : 
    1548                 :            : void
    1549                 :          0 : GlassWritableDatabase::read_position_list(GlassRePositionList* pos_list,
    1550                 :            :                                           Xapian::docid did,
    1551                 :            :                                           const string& term) const
    1552                 :            : {
    1553                 :            :     Assert(did != 0);
    1554         [ #  # ]:          0 :     string data;
    1555 [ #  # ][ #  # ]:          0 :     if (inverter.get_positionlist(did, term, data)) {
    1556         [ #  # ]:          0 :         pos_list->assign_data(std::move(data));
    1557                 :          0 :         return;
    1558                 :            :     }
    1559 [ #  # ][ #  # ]:          0 :     GlassDatabase::read_position_list(pos_list, did, term);
    1560                 :            : }
    1561                 :            : 
    1562                 :            : Xapian::termcount
    1563                 :        214 : GlassWritableDatabase::positionlist_count(Xapian::docid did,
    1564                 :            :                                           const string& term) const
    1565                 :            : {
    1566                 :            :     Assert(did != 0);
    1567         [ +  - ]:        214 :     string data;
    1568 [ +  - ][ +  + ]:        214 :     if (inverter.get_positionlist(did, term, data)) {
    1569         [ +  + ]:        176 :         if (data.empty())
    1570                 :        156 :             return 0;
    1571         [ +  - ]:         20 :         return position_table.positionlist_count(data);
    1572                 :            :     }
    1573         [ +  - ]:        214 :     return GlassDatabase::positionlist_count(did, term);
    1574                 :            : }
    1575                 :            : 
    1576                 :            : PositionList *
    1577                 :       1218 : GlassWritableDatabase::open_position_list(Xapian::docid did, const string& term) const
    1578                 :            : {
    1579                 :            :     Assert(did != 0);
    1580         [ +  - ]:       1218 :     string data;
    1581 [ +  - ][ +  + ]:       1218 :     if (inverter.get_positionlist(did, term, data)) {
    1582 [ +  - ][ +  - ]:        786 :         return new GlassPositionList(std::move(data));
    1583                 :            :     }
    1584         [ +  - ]:       1218 :     return GlassDatabase::open_position_list(did, term);
    1585                 :            : }
    1586                 :            : 
    1587                 :            : TermList *
    1588                 :        215 : GlassWritableDatabase::open_allterms(const string & prefix) const
    1589                 :            : {
    1590                 :            :     LOGCALL(DB, TermList *, "GlassWritableDatabase::open_allterms", NO_ARGS);
    1591         [ +  + ]:        215 :     if (change_count) {
    1592                 :            :         // There are changes, and terms may have been added or removed, and so
    1593                 :            :         // we need to flush changes for terms with the specified prefix (but
    1594                 :            :         // don't commit - there may be a transaction in progress).
    1595                 :        124 :         inverter.flush_post_lists(postlist_table, prefix);
    1596         [ +  + ]:        124 :         if (prefix.empty()) {
    1597                 :            :             // We've flushed all the posting list changes, but the positions,
    1598                 :            :             // document lengths and stats haven't been written, so set
    1599                 :            :             // change_count to 1.
    1600                 :            :             // FIXME: Can we handle this better?
    1601                 :         68 :             change_count = 1;
    1602                 :            :         }
    1603                 :            :     }
    1604                 :        215 :     RETURN(GlassDatabase::open_allterms(prefix));
    1605                 :            : }
    1606                 :            : 
    1607                 :            : void
    1608                 :        261 : GlassWritableDatabase::cancel()
    1609                 :            : {
    1610                 :        261 :     GlassDatabase::cancel();
    1611                 :        247 :     inverter.clear();
    1612                 :        247 :     value_stats.clear();
    1613                 :        247 :     change_count = 0;
    1614                 :        247 : }
    1615                 :            : 
    1616                 :            : void
    1617                 :         71 : GlassWritableDatabase::add_spelling(const string & word,
    1618                 :            :                                     Xapian::termcount freqinc) const
    1619                 :            : {
    1620                 :         71 :     spelling_table.add_word(word, freqinc);
    1621                 :         70 : }
    1622                 :            : 
    1623                 :            : Xapian::termcount
    1624                 :         33 : GlassWritableDatabase::remove_spelling(const string & word,
    1625                 :            :                                        Xapian::termcount freqdec) const
    1626                 :            : {
    1627                 :         33 :     return spelling_table.remove_word(word, freqdec);
    1628                 :            : }
    1629                 :            : 
    1630                 :            : TermList *
    1631                 :          5 : GlassWritableDatabase::open_spelling_wordlist() const
    1632                 :            : {
    1633                 :          5 :     spelling_table.merge_changes();
    1634                 :          5 :     return GlassDatabase::open_spelling_wordlist();
    1635                 :            : }
    1636                 :            : 
    1637                 :            : TermList *
    1638                 :      30881 : GlassWritableDatabase::open_synonym_keylist(const string & prefix) const
    1639                 :            : {
    1640                 :      30881 :     synonym_table.merge_changes();
    1641                 :      30879 :     return GlassDatabase::open_synonym_keylist(prefix);
    1642                 :            : }
    1643                 :            : 
    1644                 :            : void
    1645                 :        144 : GlassWritableDatabase::add_synonym(const string & term,
    1646                 :            :                                    const string & synonym) const
    1647                 :            : {
    1648                 :        144 :     synonym_table.add_synonym(term, synonym);
    1649                 :        142 : }
    1650                 :            : 
    1651                 :            : void
    1652                 :          5 : GlassWritableDatabase::remove_synonym(const string & term,
    1653                 :            :                                       const string & synonym) const
    1654                 :            : {
    1655                 :          5 :     synonym_table.remove_synonym(term, synonym);
    1656                 :          3 : }
    1657                 :            : 
    1658                 :            : void
    1659                 :          5 : GlassWritableDatabase::clear_synonyms(const string & term) const
    1660                 :            : {
    1661                 :          5 :     synonym_table.clear_synonyms(term);
    1662                 :          3 : }
    1663                 :            : 
    1664                 :            : void
    1665                 :         87 : GlassWritableDatabase::set_metadata(const string & key, const string & value)
    1666                 :            : {
    1667                 :            :     LOGCALL_VOID(DB, "GlassWritableDatabase::set_metadata", key | value);
    1668         [ +  - ]:         87 :     string btree_key("\x00\xc0", 2);
    1669         [ +  - ]:         87 :     btree_key += key;
    1670         [ +  + ]:         87 :     if (value.empty()) {
    1671         [ +  - ]:         12 :         postlist_table.del(btree_key);
    1672                 :            :     } else {
    1673 [ +  - ][ +  + ]:         75 :         postlist_table.add(btree_key, value);
    1674                 :         87 :     }
    1675                 :         85 : }
    1676                 :            : 
    1677                 :            : void
    1678                 :     235873 : GlassWritableDatabase::invalidate_doc_object(Xapian::Document::Internal * obj) const
    1679                 :            : {
    1680         [ +  + ]:     235873 :     if (obj == modify_shortcut_document) {
    1681                 :     208111 :         modify_shortcut_document = NULL;
    1682                 :     208111 :         modify_shortcut_docid = 0;
    1683                 :            :     }
    1684                 :     235873 : }
    1685                 :            : 
    1686                 :            : bool
    1687                 :         10 : GlassWritableDatabase::has_uncommitted_changes() const
    1688                 :            : {
    1689         [ +  - ]:          6 :     return change_count > 0 ||
    1690         [ +  - ]:         12 :            postlist_table.is_modified() ||
    1691         [ +  - ]:         12 :            position_table.is_modified() ||
    1692         [ +  - ]:         12 :            termlist_table.is_modified() ||
    1693         [ +  - ]:         12 :            value_manager.is_modified() ||
    1694         [ +  - ]:         12 :            synonym_table.is_modified() ||
    1695 [ +  + ][ -  + ]:         22 :            spelling_table.is_modified() ||
    1696                 :         16 :            docdata_table.is_modified();
    1697                 :            : }
    1698                 :            : 
    1699                 :            : #ifdef DISABLE_GPL_LIBXAPIAN
    1700                 :            : # error GPL source we cannot relicense included in libxapian
    1701                 :            : #endif

Generated by: LCOV version 1.11