LCOV - code coverage report
Current view: top level - api - compactor.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core eba1a2e3082b Lines: 143 179 79.9 %
Date: 2019-06-13 13:35:36 Functions: 3 7 42.9 %
Branches: 147 314 46.8 %

           Branch data     Line data    Source code
       1                 :            : /** @file compactor.cc
       2                 :            :  * @brief Compact a database, or merge and compact several.
       3                 :            :  */
       4                 :            : /* Copyright (C) 2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2015,2016,2017,2018 Olly Betts
       5                 :            :  * Copyright (C) 2008 Lemur Consulting Ltd
       6                 :            :  *
       7                 :            :  * This program is free software; you can redistribute it and/or
       8                 :            :  * modify it under the terms of the GNU General Public License as
       9                 :            :  * published by the Free Software Foundation; either version 2 of the
      10                 :            :  * License, or (at your option) any later version.
      11                 :            :  *
      12                 :            :  * This program is distributed in the hope that it will be useful,
      13                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      14                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15                 :            :  * GNU General Public License for more details.
      16                 :            :  *
      17                 :            :  * You should have received a copy of the GNU General Public License
      18                 :            :  * along with this program; if not, write to the Free Software
      19                 :            :  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
      20                 :            :  * USA
      21                 :            :  */
      22                 :            : 
      23                 :            : #include <config.h>
      24                 :            : 
      25                 :            : #include <xapian/compactor.h>
      26                 :            : 
      27                 :            : #include <algorithm>
      28                 :            : #include <fstream>
      29                 :            : #include <vector>
      30                 :            : 
      31                 :            : #include <cerrno>
      32                 :            : #include <cstring>
      33                 :            : #include <ctime>
      34                 :            : #include "safesysstat.h"
      35                 :            : #include <sys/types.h>
      36                 :            : 
      37                 :            : #include "safeunistd.h"
      38                 :            : #include "safefcntl.h"
      39                 :            : 
      40                 :            : #include "backends/backends.h"
      41                 :            : #include "backends/databaseinternal.h"
      42                 :            : #include "debuglog.h"
      43                 :            : #include "leafpostlist.h"
      44                 :            : #include "omassert.h"
      45                 :            : #include "filetests.h"
      46                 :            : #include "fileutils.h"
      47                 :            : #include "io_utils.h"
      48                 :            : #include "stringutils.h"
      49                 :            : #include "str.h"
      50                 :            : 
      51                 :            : #ifdef XAPIAN_HAS_GLASS_BACKEND
      52                 :            : #include "backends/glass/glass_database.h"
      53                 :            : #include "backends/glass/glass_version.h"
      54                 :            : #endif
      55                 :            : 
      56                 :            : #ifdef XAPIAN_HAS_HONEY_BACKEND
      57                 :            : #include "backends/honey/honey_database.h"
      58                 :            : #include "backends/honey/honey_version.h"
      59                 :            : #endif
      60                 :            : 
      61                 :            : #include "backends/multi/multi_database.h"
      62                 :            : 
      63                 :            : #include <xapian/constants.h>
      64                 :            : #include <xapian/database.h>
      65                 :            : #include <xapian/error.h>
      66                 :            : 
      67                 :            : using namespace std;
      68                 :            : 
      69                 :            : class CmpByFirstUsed {
      70                 :            :     const vector<pair<Xapian::docid, Xapian::docid>>& used_ranges;
      71                 :            : 
      72                 :            :   public:
      73                 :            :     explicit
      74                 :          8 :     CmpByFirstUsed(const vector<pair<Xapian::docid, Xapian::docid>>& ur)
      75                 :          8 :         : used_ranges(ur) { }
      76                 :            : 
      77                 :         21 :     bool operator()(size_t a, size_t b) const {
      78                 :         21 :         return used_ranges[a].first < used_ranges[b].first;
      79                 :            :     }
      80                 :            : };
      81                 :            : 
      82                 :            : namespace Xapian {
      83                 :            : 
      84         [ #  # ]:          0 : Compactor::~Compactor() { }
      85                 :            : 
      86                 :            : void
      87                 :          0 : Compactor::set_status(const string & table, const string & status)
      88                 :            : {
      89                 :            :     (void)table;
      90                 :            :     (void)status;
      91                 :          0 : }
      92                 :            : 
      93                 :            : string
      94                 :          0 : Compactor::resolve_duplicate_metadata(const string & key,
      95                 :            :                                       size_t num_tags, const std::string tags[])
      96                 :            : {
      97                 :            :     (void)key;
      98                 :            :     (void)num_tags;
      99                 :          0 :     return tags[0];
     100                 :            : }
     101                 :            : 
     102                 :            : }
     103                 :            : 
     104                 :            : [[noreturn]]
     105                 :            : static void
     106                 :          0 : backend_mismatch(const Xapian::Database::Internal* db, int backend1,
     107                 :            :                  const string &dbpath2, int backend2)
     108                 :            : {
     109         [ #  # ]:          0 :     string dbpath1;
     110         [ #  # ]:          0 :     db->get_backend_info(&dbpath1);
     111         [ #  # ]:          0 :     string msg = "All databases must be the same type ('";
     112         [ #  # ]:          0 :     msg += dbpath1;
     113         [ #  # ]:          0 :     msg += "' is ";
     114         [ #  # ]:          0 :     msg += backend_name(backend1);
     115         [ #  # ]:          0 :     msg += ", but '";
     116         [ #  # ]:          0 :     msg += dbpath2;
     117         [ #  # ]:          0 :     msg += "' is ";
     118         [ #  # ]:          0 :     msg += backend_name(backend2);
     119         [ #  # ]:          0 :     msg += ')';
     120 [ #  # ][ #  # ]:          0 :     throw Xapian::InvalidArgumentError(msg);
     121                 :            : }
     122                 :            : 
     123                 :            : namespace Xapian {
     124                 :            : 
     125                 :            : void
     126                 :        371 : Database::compact_(const string * output_ptr, int fd, unsigned flags,
     127                 :            :                    int block_size,
     128                 :            :                    Xapian::Compactor * compactor) const
     129                 :            : {
     130                 :            :     LOGCALL_VOID(API, "Database::compact_", output_ptr | fd | flags | block_size | compactor);
     131                 :            : 
     132                 :        371 :     bool renumber = !(flags & DBCOMPACT_NO_RENUMBER);
     133                 :            : 
     134                 :        371 :     enum { STUB_NO, STUB_FILE, STUB_DIR } compact_to_stub = STUB_NO;
     135         [ +  - ]:        371 :     string destdir;
     136         [ +  + ]:        371 :     if (output_ptr) {
     137                 :            :         // We need a modifiable destdir in this function.
     138         [ +  - ]:        363 :         destdir = *output_ptr;
     139         [ +  + ]:        363 :         if (!(flags & DBCOMPACT_SINGLE_FILE)) {
     140         [ +  + ]:        337 :             if (file_exists(destdir)) {
     141                 :            :                 // Stub file.
     142                 :          4 :                 compact_to_stub = STUB_FILE;
     143 [ +  - ][ +  + ]:        333 :             } else if (file_exists(destdir + "/XAPIANDB")) {
     144                 :            :                 // Stub directory.
     145                 :          4 :                 compact_to_stub = STUB_DIR;
     146                 :            :             }
     147                 :            :         }
     148                 :            :     } else {
     149                 :            :         // Single file is implied when writing to a file descriptor.
     150                 :          8 :         flags |= DBCOMPACT_SINGLE_FILE;
     151                 :            :     }
     152                 :            : 
     153         [ +  - ]:        371 :     auto n_shards = internal->size();
     154                 :        371 :     Xapian::docid tot_off = 0;
     155                 :        371 :     Xapian::docid last_docid = 0;
     156                 :            : 
     157                 :        742 :     vector<Xapian::docid> offset;
     158                 :        742 :     vector<pair<Xapian::docid, Xapian::docid>> used_ranges;
     159                 :        742 :     vector<const Xapian::Database::Internal*> internals;
     160         [ +  - ]:        371 :     offset.reserve(n_shards);
     161         [ +  - ]:        371 :     used_ranges.reserve(n_shards);
     162         [ +  - ]:        371 :     internals.reserve(n_shards);
     163                 :            : 
     164         [ +  + ]:        371 :     if (n_shards > 1) {
     165                 :         46 :         auto multi_db = static_cast<MultiDatabase*>(internal.get());
     166         [ +  + ]:        167 :         for (auto&& db : multi_db->shards) {
     167         [ +  - ]:        121 :             internals.push_back(db);
     168                 :            :         }
     169                 :            :     } else {
     170         [ +  - ]:        325 :         internals.push_back(internal.get());
     171                 :            :     }
     172                 :            : 
     173                 :        371 :     int backend = BACKEND_UNKNOWN;
     174         [ +  + ]:        817 :     for (auto&& shard : internals) {
     175         [ +  - ]:        446 :         string srcdir;
     176         [ +  - ]:        446 :         int type = shard->get_backend_info(&srcdir);
     177                 :            :         // Check destdir isn't the same as any source directory, unless it
     178                 :            :         // is a stub database or we're compacting to an fd.
     179 [ +  + ][ +  + ]:        446 :         if (!compact_to_stub && !destdir.empty() && srcdir == destdir) {
         [ +  - ][ -  + ]
                 [ -  + ]
     180                 :            :             throw InvalidArgumentError("destination may not be the same as "
     181                 :            :                                        "any source database, unless it is a "
     182 [ #  # ][ #  # ]:          0 :                                        "stub database");
                 [ #  # ]
     183                 :            :         }
     184      [ +  +  - ]:        446 :         switch (type) {
     185                 :            :             case BACKEND_GLASS:
     186 [ +  + ][ -  + ]:        431 :                 if (backend != type && backend != BACKEND_UNKNOWN) {
     187                 :          0 :                     backend_mismatch(internals[0], backend, srcdir, type);
     188                 :            :                 }
     189                 :        431 :                 backend = type;
     190                 :        431 :                 break;
     191                 :            :             case BACKEND_HONEY:
     192 [ +  + ][ -  + ]:         15 :                 if (backend != type && backend != BACKEND_UNKNOWN) {
     193                 :          0 :                     backend_mismatch(internals[0], backend, srcdir, type);
     194                 :            :                 }
     195                 :         15 :                 backend = type;
     196                 :         15 :                 break;
     197                 :            :             default:
     198                 :            :                 throw DatabaseError("Only glass and honey databases can be "
     199 [ #  # ][ #  # ]:          0 :                                     "compacted");
                 [ #  # ]
     200                 :            :         }
     201                 :            : 
     202                 :        446 :         Xapian::docid first = 0, last = 0;
     203                 :            : 
     204                 :            :         // "Empty" databases might have spelling or synonym data so can't
     205                 :            :         // just be completely ignored.
     206         [ +  - ]:        446 :         Xapian::doccount num_docs = shard->get_doccount();
     207         [ +  + ]:        446 :         if (num_docs != 0) {
     208         [ +  - ]:        421 :             shard->get_used_docid_range(first, last);
     209                 :            : 
     210 [ +  + ][ +  - ]:        421 :             if (renumber && first) {
     211                 :            :                 // Prune any unused docids off the start of this source
     212                 :            :                 // database.
     213                 :            :                 //
     214                 :            :                 // tot_off could wrap here, but it's unsigned, so that's
     215                 :            :                 // OK.
     216                 :        399 :                 tot_off -= (first - 1);
     217                 :            :             }
     218                 :            : 
     219                 :            : #ifdef XAPIAN_ASSERTIONS
     220                 :            :             PostList* pl = shard->open_post_list(string());
     221                 :            :             pl->next();
     222                 :            :             // This test should never fail, since shard->get_doccount() is
     223                 :            :             // non-zero!
     224                 :            :             Assert(!pl->at_end());
     225                 :            :             AssertEq(pl->get_docid(), first);
     226                 :            :             AssertRel(last,>=,first);
     227                 :            :             pl->skip_to(last);
     228                 :            :             Assert(!pl->at_end());
     229                 :            :             AssertEq(pl->get_docid(), last);
     230                 :            :             pl->next();
     231                 :            :             Assert(pl->at_end());
     232                 :            :             delete pl;
     233                 :            : #endif
     234                 :            :         }
     235                 :            : 
     236         [ +  - ]:        446 :         offset.push_back(tot_off);
     237         [ +  + ]:        446 :         if (renumber)
     238                 :        424 :             tot_off += last;
     239 [ +  - ][ +  + ]:         22 :         else if (last_docid < shard->get_lastdocid())
     240         [ +  - ]:         15 :             last_docid = shard->get_lastdocid();
     241 [ +  - ][ +  - ]:        446 :         used_ranges.push_back(make_pair(first, last));
     242                 :        446 :     }
     243                 :            : 
     244         [ +  + ]:        371 :     if (renumber)
     245                 :        362 :         last_docid = tot_off;
     246                 :            : 
     247 [ +  + ][ +  + ]:        371 :     if (!renumber && n_shards > 1) {
     248                 :            :         // We want to process the sources in ascending order of first
     249                 :            :         // docid.  So we create a vector "order" with ascending integers
     250                 :            :         // and then sort so the indirected order is right.
     251                 :          8 :         vector<size_t> order;
     252         [ +  - ]:          8 :         order.reserve(n_shards);
     253         [ +  + ]:         29 :         for (size_t i = 0; i < n_shards; ++i)
     254         [ +  - ]:         21 :             order.push_back(i);
     255                 :            : 
     256         [ +  - ]:          8 :         sort(order.begin(), order.end(), CmpByFirstUsed(used_ranges));
     257                 :            : 
     258                 :            :         // Now use order to reorder internals to be in ascending order by first
     259                 :            :         // docid, and while we're at it check the ranges are disjoint.
     260                 :         16 :         vector<const Xapian::Database::Internal*> internals_;
     261         [ +  - ]:          8 :         internals_.reserve(n_shards);
     262                 :         16 :         vector<pair<Xapian::docid, Xapian::docid>> used_ranges_;
     263         [ +  - ]:          8 :         used_ranges_.reserve(n_shards);
     264                 :            : 
     265                 :          8 :         Xapian::docid last_start = 0, last_end = 0;
     266         [ +  + ]:         21 :         for (size_t j = 0; j != order.size(); ++j) {
     267                 :         18 :             size_t n = order[j];
     268                 :            : 
     269         [ +  - ]:         18 :             internals_.push_back(internals[n]);
     270         [ +  - ]:         18 :             used_ranges_.push_back(used_ranges[n]);
     271                 :            : 
     272                 :         18 :             const pair<Xapian::docid, Xapian::docid> p = used_ranges[n];
     273                 :            :             // Skip empty databases.
     274 [ -  + ][ #  # ]:         18 :             if (p.first == 0 && p.second == 0)
     275                 :          0 :                 continue;
     276                 :            :             // Check for overlap with the previous database's range.
     277         [ +  + ]:         18 :             if (p.first <= last_end) {
     278         [ +  - ]:          5 :                 string tmp;
     279         [ +  - ]:         10 :                 string msg = "when merging databases, --no-renumber is only currently supported if the databases have disjoint ranges of used document ids: ";
     280         [ +  - ]:          5 :                 internals_[j - 1]->get_backend_info(&tmp);
     281         [ +  - ]:          5 :                 msg += tmp;
     282         [ +  - ]:          5 :                 msg += " has range ";
     283 [ +  - ][ +  - ]:          5 :                 msg += str(last_start);
     284         [ +  - ]:          5 :                 msg += '-';
     285 [ +  - ][ +  - ]:          5 :                 msg += str(last_end);
     286         [ +  - ]:          5 :                 msg += ", ";
     287         [ +  - ]:          5 :                 internals_[j]->get_backend_info(&tmp);
     288         [ +  - ]:          5 :                 msg += tmp;
     289         [ +  - ]:          5 :                 msg += " has range ";
     290 [ +  - ][ +  - ]:          5 :                 msg += str(p.first);
     291         [ +  - ]:          5 :                 msg += '-';
     292 [ +  - ][ +  - ]:          5 :                 msg += str(p.second);
     293 [ +  - ][ +  - ]:         10 :                 throw Xapian::InvalidOperationError(msg);
     294                 :            :             }
     295                 :         13 :             last_start = p.first;
     296                 :         13 :             last_end = p.second;
     297                 :            :         }
     298                 :            : 
     299         [ +  - ]:          3 :         swap(internals, internals_);
     300         [ +  - ]:         11 :         swap(used_ranges, used_ranges_);
     301                 :            :     }
     302                 :            : 
     303         [ +  - ]:        366 :     string stub_file;
     304         [ +  + ]:        366 :     if (compact_to_stub) {
     305         [ +  - ]:          8 :         stub_file = destdir;
     306         [ +  + ]:          8 :         if (compact_to_stub == STUB_DIR) {
     307         [ +  - ]:          4 :             stub_file += "/XAPIANDB";
     308         [ +  - ]:          4 :             destdir += '/';
     309                 :            :         } else {
     310         [ +  - ]:          4 :             destdir += '_';
     311                 :            :         }
     312                 :          8 :         size_t sfx = destdir.size();
     313                 :          8 :         time_t now = time(NULL);
     314                 :            :         while (true) {
     315         [ +  - ]:          8 :             destdir.resize(sfx);
     316 [ +  - ][ +  - ]:          8 :             destdir += str(now++);
     317         [ +  - ]:          8 :             if (mkdir(destdir.c_str(), 0755) == 0)
     318                 :          8 :                 break;
     319         [ #  # ]:          0 :             if (errno != EEXIST) {
     320         [ #  # ]:          0 :                 string msg = destdir;
     321         [ #  # ]:          0 :                 msg += ": mkdir failed";
     322         [ #  # ]:          0 :                 throw Xapian::DatabaseError(msg, errno);
     323                 :            :             }
     324                 :          8 :         }
     325         [ +  + ]:        358 :     } else if (!(flags & Xapian::DBCOMPACT_SINGLE_FILE)) {
     326                 :            :         // If the destination database directory doesn't exist, create it.
     327         [ +  + ]:        324 :         if (mkdir(destdir.c_str(), 0755) < 0) {
     328                 :            :             // Check why mkdir failed.  It's ok if the directory already
     329                 :            :             // exists, but we also get EEXIST if there's an existing file with
     330                 :            :             // that name.
     331                 :        258 :             int mkdir_errno = errno;
     332 [ +  - ][ -  + ]:        258 :             if (mkdir_errno != EEXIST || !dir_exists(destdir)) {
                 [ -  + ]
     333         [ #  # ]:          0 :                 string msg = destdir;
     334         [ #  # ]:          0 :                 msg += ": cannot create directory";
     335         [ #  # ]:        324 :                 throw Xapian::DatabaseError(msg, mkdir_errno);
     336                 :            :             }
     337                 :            :         }
     338                 :            :     }
     339                 :            : 
     340                 :            : #if defined XAPIAN_HAS_GLASS_BACKEND || defined XAPIAN_HAS_HONEY_BACKEND
     341                 :            :     Xapian::Compactor::compaction_level compaction =
     342                 :        366 :         static_cast<Xapian::Compactor::compaction_level>(flags & (Xapian::Compactor::STANDARD|Xapian::Compactor::FULL|Xapian::Compactor::FULLER));
     343                 :            : #else
     344                 :            :     (void)compactor;
     345                 :            :     (void)block_size;
     346                 :            : #endif
     347                 :            : 
     348                 :        366 :     auto output_backend = flags & Xapian::DB_BACKEND_MASK_;
     349         [ +  + ]:        366 :     if (backend == BACKEND_GLASS) {
     350      [ +  +  - ]:        357 :         switch (output_backend) {
     351                 :            :             case 0:
     352                 :            :             case Xapian::DB_BACKEND_GLASS:
     353                 :            : #ifdef XAPIAN_HAS_GLASS_BACKEND
     354         [ +  + ]:         68 :                 if (output_ptr) {
     355                 :            :                     GlassDatabase::compact(compactor, destdir.c_str(), 0,
     356                 :            :                                            internals, offset,
     357                 :            :                                            block_size, compaction, flags,
     358         [ +  + ]:         62 :                                            last_docid);
     359                 :            :                 } else {
     360                 :            :                     GlassDatabase::compact(compactor, NULL, fd,
     361                 :            :                                            internals, offset,
     362                 :            :                                            block_size, compaction, flags,
     363         [ +  - ]:          6 :                                            last_docid);
     364                 :            :                 }
     365                 :         64 :                 break;
     366                 :            : #else
     367                 :            :                 (void)fd;
     368                 :            :                 (void)last_docid;
     369                 :            :                 throw Xapian::FeatureUnavailableError("Glass backend disabled "
     370                 :            :                                                       "at build time");
     371                 :            : #endif
     372                 :            :             case Xapian::DB_BACKEND_HONEY:
     373                 :            :                 // Honey isn't block based.
     374                 :            :                 (void)block_size;
     375                 :            : #ifdef XAPIAN_HAS_HONEY_BACKEND
     376         [ +  - ]:        289 :                 if (output_ptr) {
     377                 :            :                     HoneyDatabase::compact(compactor, destdir.c_str(), 0,
     378                 :            :                                            Xapian::DB_BACKEND_GLASS,
     379                 :            :                                            internals, offset,
     380                 :            :                                            compaction, flags,
     381         [ +  - ]:        289 :                                            last_docid);
     382                 :            :                 } else {
     383                 :            :                     HoneyDatabase::compact(compactor, NULL, fd,
     384                 :            :                                            Xapian::DB_BACKEND_GLASS,
     385                 :            :                                            internals, offset,
     386                 :            :                                            compaction, flags,
     387         [ #  # ]:          0 :                                            last_docid);
     388                 :            :                 }
     389                 :        289 :                 break;
     390                 :            : #else
     391                 :            :                 (void)fd;
     392                 :            :                 (void)last_docid;
     393                 :            :                 throw Xapian::FeatureUnavailableError("Honey backend disabled "
     394                 :            :                                                       "at build time");
     395                 :            : #endif
     396                 :            :             default:
     397                 :            :                 throw Xapian::UnimplementedError("Glass can only be "
     398                 :            :                                                  "compacted to itself or "
     399 [ #  # ][ #  # ]:        353 :                                                  "honey");
                 [ #  # ]
     400                 :            :         }
     401         [ +  - ]:          9 :     } else if (backend == BACKEND_HONEY) {
     402         [ +  - ]:          9 :         switch (output_backend) {
     403                 :            :             case 0:
     404                 :            :             case Xapian::DB_BACKEND_HONEY:
     405                 :            : #ifdef XAPIAN_HAS_HONEY_BACKEND
     406                 :            :                 // Honey isn't block based.
     407                 :            :                 (void)block_size;
     408         [ +  + ]:          9 :                 if (output_ptr) {
     409                 :            :                     HoneyDatabase::compact(compactor, destdir.c_str(), 0,
     410                 :            :                                            Xapian::DB_BACKEND_HONEY,
     411                 :            :                                            internals, offset,
     412                 :            :                                            compaction, flags,
     413         [ +  - ]:          7 :                                            last_docid);
     414                 :            :                 } else {
     415                 :            :                     HoneyDatabase::compact(compactor, NULL, fd,
     416                 :            :                                            Xapian::DB_BACKEND_HONEY,
     417                 :            :                                            internals, offset,
     418                 :            :                                            compaction, flags,
     419         [ +  - ]:          2 :                                            last_docid);
     420                 :            :                 }
     421                 :          9 :                 break;
     422                 :            : #else
     423                 :            :                 (void)fd;
     424                 :            :                 (void)last_docid;
     425                 :            :                 throw Xapian::FeatureUnavailableError("Honey backend disabled "
     426                 :            :                                                       "at build time");
     427                 :            : #endif
     428                 :            :             default:
     429                 :            :                 throw Xapian::UnimplementedError("Honey can only be "
     430 [ #  # ][ #  # ]:          9 :                                                  "compacted to itself");
                 [ #  # ]
     431                 :            :         }
     432                 :            :     }
     433                 :            : 
     434         [ +  + ]:        362 :     if (compact_to_stub) {
     435         [ +  - ]:          8 :         string new_stub_file = destdir;
     436         [ +  - ]:          8 :         new_stub_file += "/new_stub.tmp";
     437                 :            :         {
     438         [ +  - ]:          8 :             ofstream new_stub(new_stub_file.c_str());
     439                 :          8 :             size_t slash = destdir.find_last_of(DIR_SEPS);
     440 [ +  - ][ +  - ]:          8 :             new_stub << "auto " << destdir.substr(slash + 1) << '\n';
         [ +  - ][ +  - ]
     441                 :            :         }
     442 [ +  - ][ -  + ]:          8 :         if (!io_tmp_rename(new_stub_file, stub_file)) {
     443         [ #  # ]:          0 :             string msg = "Cannot rename '";
     444         [ #  # ]:          0 :             msg += new_stub_file;
     445         [ #  # ]:          0 :             msg += "' to '";
     446         [ #  # ]:          0 :             msg += stub_file;
     447         [ #  # ]:          0 :             msg += '\'';
     448         [ #  # ]:          0 :             throw Xapian::DatabaseError(msg, errno);
     449                 :          8 :         }
     450                 :        737 :     }
     451                 :        362 : }
     452                 :            : 
     453                 :            : }

Generated by: LCOV version 1.11