LCOV - code coverage report
Current view: top level - backends/glass - glass_postlist.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core 954b5873a738 Lines: 445 531 83.8 %
Date: 2019-06-30 05:20:33 Functions: 44 46 95.7 %
Branches: 393 774 50.8 %

           Branch data     Line data    Source code
       1                 :            : /** @file glass_postlist.cc
       2                 :            :  * @brief Postlists in a glass database
       3                 :            :  */
       4                 :            : /* Copyright 1999,2000,2001 BrightStation PLC
       5                 :            :  * Copyright 2002,2003,2004,2005,2007,2008,2009,2011,2013,2014,2015,2017,2019 Olly Betts
       6                 :            :  * Copyright 2007,2008,2009 Lemur Consulting Ltd
       7                 :            :  *
       8                 :            :  * This program is free software; you can redistribute it and/or
       9                 :            :  * modify it under the terms of the GNU General Public License as
      10                 :            :  * published by the Free Software Foundation; either version 2 of the
      11                 :            :  * License, or (at your option) any later version.
      12                 :            :  *
      13                 :            :  * This program is distributed in the hope that it will be useful,
      14                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      15                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16                 :            :  * GNU General Public License for more details.
      17                 :            :  *
      18                 :            :  * You should have received a copy of the GNU General Public License
      19                 :            :  * along with this program; if not, write to the Free Software
      20                 :            :  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
      21                 :            :  * USA
      22                 :            :  */
      23                 :            : 
      24                 :            : #include <config.h>
      25                 :            : 
      26                 :            : #include "glass_postlist.h"
      27                 :            : 
      28                 :            : #include "glass_cursor.h"
      29                 :            : #include "glass_database.h"
      30                 :            : #include "debuglog.h"
      31                 :            : #include "pack.h"
      32                 :            : #include "str.h"
      33                 :            : #include "unicode/description_append.h"
      34                 :            : 
      35                 :            : using Xapian::Internal::intrusive_ptr;
      36                 :            : 
      37                 :            : // Static functions
      38                 :            : 
      39                 :            : /// Report an error when reading the posting list.
      40                 :            : [[noreturn]]
      41                 :          0 : static void report_read_error(const char * position)
      42                 :            : {
      43         [ #  # ]:          0 :     if (position == 0) {
      44                 :            :         // data ran out
      45                 :            :         LOGLINE(DB, "GlassPostList data ran out");
      46 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseCorruptError("Data ran out unexpectedly when reading posting list.");
                 [ #  # ]
      47                 :            :     }
      48                 :            :     // overflow
      49                 :            :     LOGLINE(DB, "GlassPostList value too large");
      50 [ #  # ][ #  # ]:          0 :     throw Xapian::RangeError("Value in posting list too large.");
                 [ #  # ]
      51                 :            : }
      52                 :            : 
      53                 :            : static inline bool
      54                 :     348665 : get_tname_from_key(const char **src, const char *end, string &tname)
      55                 :            : {
      56                 :     348665 :     return unpack_string_preserving_sort(src, end, tname);
      57                 :            : }
      58                 :            : 
      59                 :            : static inline bool
      60                 :     497919 : check_tname_in_key_lite(const char **keypos, const char *keyend, const string &tname)
      61                 :            : {
      62         [ +  - ]:     497919 :     string tname_in_key;
      63                 :            : 
      64 [ +  + ][ +  + ]:     497919 :     if (keyend - *keypos >= 2 && (*keypos)[0] == '\0' && (*keypos)[1] == '\xe0') {
                 [ +  - ]
      65                 :     149254 :         *keypos += 2;
      66                 :            :     } else {
      67                 :            :         // Read the termname.
      68 [ +  - ][ -  + ]:     348665 :         if (!get_tname_from_key(keypos, keyend, tname_in_key))
      69                 :          0 :             report_read_error(*keypos);
      70                 :            :     }
      71                 :            : 
      72                 :            :     // This should only fail if the postlist doesn't exist at all.
      73         [ +  - ]:     497919 :     return tname_in_key == tname;
      74                 :            : }
      75                 :            : 
      76                 :            : static inline bool
      77                 :     356936 : check_tname_in_key(const char **keypos, const char *keyend, const string &tname)
      78                 :            : {
      79         [ -  + ]:     356936 :     if (*keypos == keyend) return false;
      80                 :            : 
      81                 :     356936 :     return check_tname_in_key_lite(keypos, keyend, tname);
      82                 :            : }
      83                 :            : 
      84                 :            : /// Read the start of the first chunk in the posting list.
      85                 :            : static Xapian::docid
      86                 :    1180888 : read_start_of_first_chunk(const char ** posptr,
      87                 :            :                           const char * end,
      88                 :            :                           Xapian::doccount * number_of_entries_ptr,
      89                 :            :                           Xapian::termcount * collection_freq_ptr)
      90                 :            : {
      91                 :            :     LOGCALL_STATIC(DB, Xapian::docid, "read_start_of_first_chunk", (const void *)posptr | (const void *)end | (void *)number_of_entries_ptr | (void *)collection_freq_ptr);
      92                 :            : 
      93                 :            :     GlassPostList::read_number_of_entries(posptr, end,
      94                 :            :                                           number_of_entries_ptr,
      95         [ +  - ]:    1180888 :                                           collection_freq_ptr);
      96                 :            :     if (number_of_entries_ptr)
      97                 :            :         LOGVALUE(DB, *number_of_entries_ptr);
      98                 :            :     if (collection_freq_ptr)
      99                 :            :         LOGVALUE(DB, *collection_freq_ptr);
     100                 :            : 
     101                 :            :     Xapian::docid did;
     102                 :            :     // Read the docid of the first entry in the posting list.
     103         [ -  + ]:    1180888 :     if (!unpack_uint(posptr, end, &did))
     104                 :          0 :         report_read_error(*posptr);
     105                 :    1180888 :     ++did;
     106                 :            :     LOGVALUE(DB, did);
     107                 :    1180888 :     RETURN(did);
     108                 :            : }
     109                 :            : 
     110                 :            : static inline void
     111                 :   74849188 : read_did_increase(const char ** posptr, const char * end,
     112                 :            :                   Xapian::docid * did_ptr)
     113                 :            : {
     114                 :            :     Xapian::docid did_increase;
     115         [ -  + ]:   74849188 :     if (!unpack_uint(posptr, end, &did_increase)) report_read_error(*posptr);
     116                 :   74849188 :     *did_ptr += did_increase + 1;
     117                 :   74849188 : }
     118                 :            : 
     119                 :            : /// Read the wdf for an entry.
     120                 :            : static inline void
     121                 :   75182967 : read_wdf(const char ** posptr, const char * end, Xapian::termcount * wdf_ptr)
     122                 :            : {
     123         [ -  + ]:   75182967 :     if (!unpack_uint(posptr, end, wdf_ptr)) report_read_error(*posptr);
     124                 :   75182967 : }
     125                 :            : 
     126                 :            : /// Read the start of a chunk.
     127                 :            : static Xapian::docid
     128                 :    1111393 : read_start_of_chunk(const char ** posptr,
     129                 :            :                     const char * end,
     130                 :            :                     Xapian::docid first_did_in_chunk,
     131                 :            :                     bool * is_last_chunk_ptr)
     132                 :            : {
     133                 :            :     LOGCALL_STATIC(DB, Xapian::docid, "read_start_of_chunk", reinterpret_cast<const void*>(posptr) | reinterpret_cast<const void*>(end) | first_did_in_chunk | reinterpret_cast<const void*>(is_last_chunk_ptr));
     134                 :            :     Assert(is_last_chunk_ptr);
     135                 :            : 
     136                 :            :     // Read whether this is the last chunk
     137         [ -  + ]:    1111393 :     if (!unpack_bool(posptr, end, is_last_chunk_ptr))
     138                 :          0 :         report_read_error(*posptr);
     139                 :            :     LOGVALUE(DB, *is_last_chunk_ptr);
     140                 :            : 
     141                 :            :     // Read what the final document ID in this chunk is.
     142                 :            :     Xapian::docid increase_to_last;
     143         [ -  + ]:    1111393 :     if (!unpack_uint(posptr, end, &increase_to_last))
     144                 :          0 :         report_read_error(*posptr);
     145                 :    1111393 :     Xapian::docid last_did_in_chunk = first_did_in_chunk + increase_to_last;
     146                 :            :     LOGVALUE(DB, last_did_in_chunk);
     147                 :    1111393 :     RETURN(last_did_in_chunk);
     148                 :            : }
     149                 :            : 
     150                 :            : void
     151                 :     651848 : GlassPostListTable::get_freqs(const string & term,
     152                 :            :                               Xapian::doccount * termfreq_ptr,
     153                 :            :                               Xapian::termcount * collfreq_ptr,
     154                 :            :                               Xapian::termcount * wdfub_ptr) const
     155                 :            : {
     156         [ +  - ]:     651848 :     string key = make_key(term);
     157         [ +  - ]:    1303696 :     string tag;
     158 [ +  + ][ +  + ]:     651848 :     if (!get_exact_entry(key, tag)) {
     159         [ +  + ]:      76636 :         if (termfreq_ptr)
     160                 :      26500 :             *termfreq_ptr = 0;
     161         [ +  + ]:      76636 :         if (collfreq_ptr)
     162                 :      25946 :             *collfreq_ptr = 0;
     163         [ +  + ]:      76636 :         if (wdfub_ptr)
     164                 :      48801 :             *wdfub_ptr = 0;
     165                 :            :     } else {
     166                 :     575196 :         const char * p = tag.data();
     167                 :     575196 :         const char * e = p + tag.size();
     168                 :            :         Xapian::doccount tf;
     169                 :            :         Xapian::termcount cf;
     170         [ +  - ]:     575196 :         GlassPostList::read_number_of_entries(&p, e, &tf, &cf);
     171         [ +  + ]:     575196 :         if (termfreq_ptr)
     172                 :     299737 :             *termfreq_ptr = tf;
     173         [ +  + ]:     575196 :         if (collfreq_ptr)
     174                 :     196348 :             *collfreq_ptr = cf;
     175         [ +  + ]:     575196 :         if (wdfub_ptr) {
     176 [ +  + ][ +  + ]:     263927 :             if (cf == 0 || tf == 1) {
     177                 :       4863 :                 *wdfub_ptr = cf;
     178                 :            :             } else {
     179                 :            :                 Xapian::docid did;
     180         [ -  + ]:     259064 :                 if (!unpack_uint(&p, e, &did))
     181                 :          0 :                     report_read_error(p);
     182                 :            :                 bool is_last;
     183         [ +  - ]:     259064 :                 (void)read_start_of_chunk(&p, e, did + 1, &is_last);
     184                 :            :                 (void)is_last;
     185                 :            :                 Xapian::termcount first_wdf;
     186         [ -  + ]:     259064 :                 if (!unpack_uint(&p, e, &first_wdf))
     187                 :          0 :                     report_read_error(p);
     188         [ +  - ]:     575196 :                 *wdfub_ptr = max(cf - first_wdf, first_wdf);
     189                 :            :             }
     190                 :            :         }
     191                 :     651848 :     }
     192                 :     651832 : }
     193                 :            : 
     194                 :            : Xapian::termcount
     195                 :   28886372 : GlassPostListTable::get_doclength(Xapian::docid did,
     196                 :            :                                   intrusive_ptr<const GlassDatabase> db) const {
     197         [ +  + ]:   28886372 :     if (!doclen_pl.get()) {
     198                 :            :         // Don't keep a reference back to the database, since this
     199                 :            :         // would make a reference loop.
     200 [ +  - ][ +  - ]:       1301 :         doclen_pl.reset(new GlassPostList(db, string(), false));
                 [ +  - ]
     201                 :            :     }
     202         [ +  + ]:   28886372 :     if (!doclen_pl->jump_to(did))
     203 [ +  - ][ +  - ]:      24091 :         throw Xapian::DocNotFoundError("Document " + str(did) + " not found");
         [ +  - ][ +  - ]
                 [ +  - ]
     204                 :   28862281 :     return doclen_pl->get_wdf();
     205                 :            : }
     206                 :            : 
     207                 :            : bool
     208                 :          2 : GlassPostListTable::document_exists(Xapian::docid did,
     209                 :            :                                     intrusive_ptr<const GlassDatabase> db) const
     210                 :            : {
     211         [ +  - ]:          2 :     if (!doclen_pl.get()) {
     212                 :            :         // Don't keep a reference back to the database, since this
     213                 :            :         // would make a reference loop.
     214 [ +  - ][ +  - ]:          2 :         doclen_pl.reset(new GlassPostList(db, string(), false));
                 [ -  + ]
     215                 :            :     }
     216                 :          0 :     return (doclen_pl->jump_to(did));
     217                 :            : }
     218                 :            : 
     219                 :            : // How big should chunks in the posting list be?  (They
     220                 :            : // will grow slightly bigger than this, but not more than a
     221                 :            : // few bytes extra) - FIXME: tune this value to try to
     222                 :            : // maximise how well blocks are used.  Or performance.
     223                 :            : // Or indexing speed.  Or something...
     224                 :            : const unsigned int CHUNKSIZE = 2000;
     225                 :            : 
     226                 :            : /** PostlistChunkWriter is a wrapper which acts roughly as an
     227                 :            :  *  output iterator on a postlist chunk, taking care of the
     228                 :            :  *  messy details.  It's intended to be used with deletion and
     229                 :            :  *  replacing of entries, not for adding to the end, when it's
     230                 :            :  *  not really needed.
     231                 :            :  */
     232                 :     700664 : class Glass::PostlistChunkWriter {
     233                 :            :   public:
     234                 :            :     PostlistChunkWriter(const string &orig_key_,
     235                 :            :                         bool is_first_chunk_,
     236                 :            :                         const string &tname_,
     237                 :            :                         bool is_last_chunk_);
     238                 :            : 
     239                 :            :     /// Append an entry to this chunk.
     240                 :            :     void append(GlassTable * table, Xapian::docid did,
     241                 :            :                 Xapian::termcount wdf);
     242                 :            : 
     243                 :            :     /// Append a block of raw entries to this chunk.
     244                 :     350152 :     void raw_append(Xapian::docid first_did_, Xapian::docid current_did_,
     245                 :            :                     const string & s) {
     246                 :            :         Assert(!started);
     247                 :     350152 :         first_did = first_did_;
     248                 :     350152 :         current_did = current_did_;
     249         [ +  + ]:     350152 :         if (!s.empty()) {
     250                 :     176257 :             chunk.append(s);
     251                 :     176257 :             started = true;
     252                 :            :         }
     253                 :     350152 :     }
     254                 :            : 
     255                 :            :     /** Flush the chunk to the buffered table.  Note: this may write it
     256                 :            :      *  with a different key to the original one, if for example the first
     257                 :            :      *  entry has changed.
     258                 :            :      */
     259                 :            :     void flush(GlassTable *table);
     260                 :            : 
     261                 :            :   private:
     262                 :            :     string orig_key;
     263                 :            :     string tname;
     264                 :            :     bool is_first_chunk;
     265                 :            :     bool is_last_chunk;
     266                 :            :     bool started;
     267                 :            : 
     268                 :            :     Xapian::docid first_did;
     269                 :            :     Xapian::docid current_did;
     270                 :            : 
     271                 :            :     string chunk;
     272                 :            : };
     273                 :            : 
     274                 :            : using Glass::PostlistChunkWriter;
     275                 :            : 
     276                 :            : /** PostlistChunkReader is essentially an iterator wrapper
     277                 :            :  *  around a postlist chunk.  It simply iterates through the
     278                 :            :  *  entries in a postlist.
     279                 :            :  */
     280                 :        360 : class Glass::PostlistChunkReader {
     281                 :            :     string data;
     282                 :            : 
     283                 :            :     const char *pos;
     284                 :            :     const char *end;
     285                 :            : 
     286                 :            :     bool at_end;
     287                 :            : 
     288                 :            :     Xapian::docid did;
     289                 :            :     Xapian::termcount wdf;
     290                 :            : 
     291                 :            :   public:
     292                 :            :     /** Initialise the postlist chunk reader.
     293                 :            :      *
     294                 :            :      *  @param first_did  First document id in this chunk.
     295                 :            :      *  @param data       The tag string with the header removed.
     296                 :            :      */
     297                 :        180 :     PostlistChunkReader(Xapian::docid first_did, const string & data_)
     298                 :        180 :         : data(data_), pos(data.data()), end(pos + data.length()), at_end(data.empty()), did(first_did)
     299                 :            :     {
     300 [ +  - ][ +  - ]:        180 :         if (!at_end) read_wdf(&pos, end, &wdf);
     301                 :        180 :     }
     302                 :            : 
     303                 :      28329 :     Xapian::docid get_docid() const {
     304                 :      28329 :         return did;
     305                 :            :     }
     306                 :      15304 :     Xapian::termcount get_wdf() const {
     307                 :      15304 :         return wdf;
     308                 :            :     }
     309                 :            : 
     310                 :      41554 :     bool is_at_end() const {
     311                 :      41554 :         return at_end;
     312                 :            :     }
     313                 :            : 
     314                 :            :     /** Advance to the next entry.  Set at_end if we run off the end.
     315                 :            :      */
     316                 :            :     void next();
     317                 :            : };
     318                 :            : 
     319                 :            : using Glass::PostlistChunkReader;
     320                 :            : 
     321                 :            : void
     322                 :      28320 : PostlistChunkReader::next()
     323                 :            : {
     324         [ +  + ]:      28320 :     if (pos == end) {
     325                 :        180 :         at_end = true;
     326                 :            :     } else {
     327                 :      28140 :         read_did_increase(&pos, end, &did);
     328                 :      28140 :         read_wdf(&pos, end, &wdf);
     329                 :            :     }
     330                 :      28320 : }
     331                 :            : 
     332                 :     350332 : PostlistChunkWriter::PostlistChunkWriter(const string &orig_key_,
     333                 :            :                                          bool is_first_chunk_,
     334                 :            :                                          const string &tname_,
     335                 :            :                                          bool is_last_chunk_)
     336                 :            :         : orig_key(orig_key_),
     337                 :            :           tname(tname_), is_first_chunk(is_first_chunk_),
     338                 :            :           is_last_chunk(is_last_chunk_),
     339 [ +  - ][ +  - ]:     350332 :           started(false)
     340                 :            : {
     341                 :            :     LOGCALL_CTOR(DB, "PostlistChunkWriter", orig_key_ | is_first_chunk_ | tname_ | is_last_chunk_);
     342                 :     350332 : }
     343                 :            : 
     344                 :            : void
     345                 :    2170474 : PostlistChunkWriter::append(GlassTable * table, Xapian::docid did,
     346                 :            :                             Xapian::termcount wdf)
     347                 :            : {
     348         [ +  + ]:    2170474 :     if (!started) {
     349                 :     174035 :         started = true;
     350                 :     174035 :         first_did = did;
     351                 :            :     } else {
     352                 :            :         Assert(did > current_did);
     353                 :            :         // Start a new chunk if this one has grown to the threshold.
     354         [ +  + ]:    1996439 :         if (chunk.size() >= CHUNKSIZE) {
     355                 :        243 :             bool save_is_last_chunk = is_last_chunk;
     356                 :        243 :             is_last_chunk = false;
     357                 :        243 :             flush(table);
     358                 :        243 :             is_last_chunk = save_is_last_chunk;
     359                 :        243 :             is_first_chunk = false;
     360                 :        243 :             first_did = did;
     361                 :        243 :             chunk.resize(0);
     362         [ +  - ]:        243 :             orig_key = GlassPostListTable::make_key(tname, first_did);
     363                 :            :         } else {
     364                 :    1996196 :             pack_uint(chunk, did - current_did - 1);
     365                 :            :         }
     366                 :            :     }
     367                 :    2170474 :     current_did = did;
     368                 :    2170474 :     pack_uint(chunk, wdf);
     369                 :    2170474 : }
     370                 :            : 
     371                 :            : /** Make the data to go at the start of the very first chunk.
     372                 :            :  */
     373                 :            : static inline string
     374                 :     686093 : make_start_of_first_chunk(Xapian::doccount entries,
     375                 :            :                           Xapian::termcount collectionfreq,
     376                 :            :                           Xapian::docid new_did)
     377                 :            : {
     378                 :     686093 :     string chunk;
     379         [ +  - ]:     686093 :     pack_uint(chunk, entries);
     380         [ +  - ]:     686093 :     pack_uint(chunk, collectionfreq);
     381         [ +  - ]:     686093 :     pack_uint(chunk, new_did - 1);
     382                 :     686093 :     return chunk;
     383                 :            : }
     384                 :            : 
     385                 :            : /** Make the data to go at the start of a standard chunk.
     386                 :            :  */
     387                 :            : static inline string
     388                 :     692681 : make_start_of_chunk(bool new_is_last_chunk,
     389                 :            :                     Xapian::docid new_first_did,
     390                 :            :                     Xapian::docid new_final_did)
     391                 :            : {
     392                 :            :     Assert(new_final_did >= new_first_did);
     393                 :     692681 :     string chunk;
     394         [ +  - ]:     692681 :     pack_bool(chunk, new_is_last_chunk);
     395         [ +  - ]:     692681 :     pack_uint(chunk, new_final_did - new_first_did);
     396                 :     692681 :     return chunk;
     397                 :            : }
     398                 :            : 
     399                 :            : static void
     400                 :          0 : write_start_of_chunk(string & chunk,
     401                 :            :                      unsigned int start_of_chunk_header,
     402                 :            :                      unsigned int end_of_chunk_header,
     403                 :            :                      bool is_last_chunk,
     404                 :            :                      Xapian::docid first_did_in_chunk,
     405                 :            :                      Xapian::docid last_did_in_chunk)
     406                 :            : {
     407                 :            :     Assert((size_t)(end_of_chunk_header - start_of_chunk_header) <= chunk.size());
     408                 :            : 
     409                 :            :     chunk.replace(start_of_chunk_header,
     410                 :          0 :                   end_of_chunk_header - start_of_chunk_header,
     411                 :            :                   make_start_of_chunk(is_last_chunk, first_did_in_chunk,
     412         [ #  # ]:          0 :                                       last_did_in_chunk));
     413                 :          0 : }
     414                 :            : 
     415                 :            : void
     416                 :     350575 : PostlistChunkWriter::flush(GlassTable *table)
     417                 :            : {
     418                 :            :     LOGCALL_VOID(DB, "PostlistChunkWriter::flush", table);
     419                 :            : 
     420                 :            :     /* This is one of the more messy parts involved with updating posting
     421                 :            :      * list chunks.
     422                 :            :      *
     423                 :            :      * Depending on circumstances, we may have to delete an entire chunk
     424                 :            :      * or file it under a different key, as well as possibly modifying both
     425                 :            :      * the previous and next chunk of the postlist.
     426                 :            :      */
     427                 :            : 
     428         [ +  + ]:     350575 :     if (!started) {
     429                 :            :         /* This chunk is now empty so disappears entirely.
     430                 :            :          *
     431                 :            :          * If this was the last chunk, then the previous chunk
     432                 :            :          * must have its "is_last_chunk" flag updated.
     433                 :            :          *
     434                 :            :          * If this was the first chunk, then the next chunk must
     435                 :            :          * be transformed into the first chunk.  Messy!
     436                 :            :          */
     437                 :            :         LOGLINE(DB, "PostlistChunkWriter::flush(): deleting chunk");
     438                 :            :         Assert(!orig_key.empty());
     439         [ +  - ]:         40 :         if (is_first_chunk) {
     440                 :            :             LOGLINE(DB, "PostlistChunkWriter::flush(): deleting first chunk");
     441         [ +  + ]:         40 :             if (is_last_chunk) {
     442                 :            :                 /* This is the first and the last chunk, ie the only
     443                 :            :                  * chunk, so just delete the tag.
     444                 :            :                  */
     445         [ +  - ]:         32 :                 table->del(orig_key);
     446                 :         32 :                 return;
     447                 :            :             }
     448                 :            : 
     449                 :            :             /* This is the messiest case.  The first chunk is to
     450                 :            :              * be removed, and there is at least one chunk after
     451                 :            :              * it.  Need to rewrite the next chunk as the first
     452                 :            :              * chunk.
     453                 :            :              */
     454         [ +  - ]:          8 :             unique_ptr<GlassCursor> cursor(table->cursor_get());
     455                 :            : 
     456 [ +  - ][ -  + ]:          8 :             if (!cursor->find_entry(orig_key)) {
     457 [ #  # ][ #  # ]:          0 :                 throw Xapian::DatabaseCorruptError("The key we're working on has disappeared");
                 [ #  # ]
     458                 :            :             }
     459                 :            : 
     460                 :            :             // FIXME: Currently the doclen list has a special first chunk too,
     461                 :            :             // which reduces special casing here.  The downside is a slightly
     462                 :            :             // larger than necessary first chunk and needless fiddling if the
     463                 :            :             // first chunk is deleted.  But really we should look at
     464                 :            :             // redesigning the whole postlist format with an eye to making it
     465                 :            :             // easier to update!
     466                 :            : 
     467                 :            :             // Extract existing counts from the first chunk so we can reinsert
     468                 :            :             // them into the block we're renaming.
     469                 :            :             Xapian::doccount num_ent;
     470                 :            :             Xapian::termcount coll_freq;
     471                 :            :             {
     472         [ +  - ]:          8 :                 cursor->read_tag();
     473                 :          8 :                 const char *tagpos = cursor->current_tag.data();
     474                 :          8 :                 const char *tagend = tagpos + cursor->current_tag.size();
     475                 :            : 
     476                 :            :                 (void)read_start_of_first_chunk(&tagpos, tagend,
     477         [ +  - ]:          8 :                                                 &num_ent, &coll_freq);
     478                 :            :             }
     479                 :            : 
     480                 :            :             // Seek to the next chunk.
     481         [ +  - ]:          8 :             cursor->next();
     482         [ -  + ]:          8 :             if (cursor->after_end()) {
     483 [ #  # ][ #  # ]:          0 :                 throw Xapian::DatabaseCorruptError("Expected another key but found none");
                 [ #  # ]
     484                 :            :             }
     485                 :          8 :             const char *kpos = cursor->current_key.data();
     486                 :          8 :             const char *kend = kpos + cursor->current_key.size();
     487 [ +  - ][ -  + ]:          8 :             if (!check_tname_in_key(&kpos, kend, tname)) {
     488 [ #  # ][ #  # ]:          0 :                 throw Xapian::DatabaseCorruptError("Expected another key with the same term name but found a different one");
                 [ #  # ]
     489                 :            :             }
     490                 :            : 
     491                 :            :             // Read the new first docid
     492                 :            :             Xapian::docid new_first_did;
     493         [ -  + ]:          8 :             if (!unpack_uint_preserving_sort(&kpos, kend, &new_first_did)) {
     494                 :          0 :                 report_read_error(kpos);
     495                 :            :             }
     496                 :            : 
     497         [ +  - ]:          8 :             cursor->read_tag();
     498                 :          8 :             const char *tagpos = cursor->current_tag.data();
     499                 :          8 :             const char *tagend = tagpos + cursor->current_tag.size();
     500                 :            : 
     501                 :            :             // Read the chunk header
     502                 :            :             bool new_is_last_chunk;
     503                 :            :             Xapian::docid new_last_did_in_chunk =
     504                 :            :                 read_start_of_chunk(&tagpos, tagend, new_first_did,
     505         [ +  - ]:          8 :                                     &new_is_last_chunk);
     506                 :            : 
     507         [ +  - ]:         16 :             string chunk_data(tagpos, tagend);
     508                 :            : 
     509                 :            :             // First remove the renamed tag
     510         [ +  - ]:          8 :             table->del(cursor->current_key);
     511                 :            : 
     512                 :            :             // And now write it as the first chunk
     513         [ +  - ]:         16 :             string tag;
     514 [ +  - ][ +  - ]:          8 :             tag = make_start_of_first_chunk(num_ent, coll_freq, new_first_did);
     515         [ +  - ]:         16 :             tag += make_start_of_chunk(new_is_last_chunk,
     516                 :            :                                               new_first_did,
     517         [ +  - ]:          8 :                                               new_last_did_in_chunk);
     518         [ +  - ]:          8 :             tag += chunk_data;
     519 [ +  - ][ +  - ]:          8 :             table->add(orig_key, tag);
     520                 :         48 :             return;
     521                 :            :         }
     522                 :            : 
     523                 :            :         LOGLINE(DB, "PostlistChunkWriter::flush(): deleting secondary chunk");
     524                 :            :         /* This isn't the first chunk.  Check whether we're the last chunk. */
     525                 :            : 
     526                 :            :         // Delete this chunk
     527                 :          0 :         table->del(orig_key);
     528                 :            : 
     529         [ #  # ]:          0 :         if (is_last_chunk) {
     530                 :            :             LOGLINE(DB, "PostlistChunkWriter::flush(): deleting secondary last chunk");
     531                 :            :             // Update the previous chunk's is_last_chunk flag.
     532         [ #  # ]:          0 :             unique_ptr<GlassCursor> cursor(table->cursor_get());
     533                 :            : 
     534                 :            :             /* Should not find the key we just deleted, but should
     535                 :            :              * find the previous chunk. */
     536 [ #  # ][ #  # ]:          0 :             if (cursor->find_entry(orig_key)) {
     537 [ #  # ][ #  # ]:          0 :                 throw Xapian::DatabaseCorruptError("Glass key not deleted as we expected");
                 [ #  # ]
     538                 :            :             }
     539                 :            :             // Make sure this is a chunk with the right term attached.
     540                 :          0 :             const char * keypos = cursor->current_key.data();
     541                 :          0 :             const char * keyend = keypos + cursor->current_key.size();
     542 [ #  # ][ #  # ]:          0 :             if (!check_tname_in_key(&keypos, keyend, tname)) {
     543 [ #  # ][ #  # ]:          0 :                 throw Xapian::DatabaseCorruptError("Couldn't find chunk before delete chunk");
                 [ #  # ]
     544                 :            :             }
     545                 :            : 
     546                 :          0 :             bool is_prev_first_chunk = (keypos == keyend);
     547                 :            : 
     548                 :            :             // Now update the last_chunk
     549         [ #  # ]:          0 :             cursor->read_tag();
     550         [ #  # ]:          0 :             string tag = cursor->current_tag;
     551                 :            : 
     552                 :          0 :             const char *tagpos = tag.data();
     553                 :          0 :             const char *tagend = tagpos + tag.size();
     554                 :            : 
     555                 :            :             // Skip first chunk header
     556                 :            :             Xapian::docid first_did_in_chunk;
     557         [ #  # ]:          0 :             if (is_prev_first_chunk) {
     558                 :            :                 first_did_in_chunk = read_start_of_first_chunk(&tagpos, tagend,
     559         [ #  # ]:          0 :                                                                0, 0);
     560                 :            :             } else {
     561         [ #  # ]:          0 :                 if (!unpack_uint_preserving_sort(&keypos, keyend, &first_did_in_chunk))
     562                 :          0 :                     report_read_error(keypos);
     563                 :            :             }
     564                 :            :             bool wrong_is_last_chunk;
     565                 :          0 :             string::size_type start_of_chunk_header = tagpos - tag.data();
     566                 :            :             Xapian::docid last_did_in_chunk =
     567                 :            :                 read_start_of_chunk(&tagpos, tagend, first_did_in_chunk,
     568         [ #  # ]:          0 :                                     &wrong_is_last_chunk);
     569                 :          0 :             string::size_type end_of_chunk_header = tagpos - tag.data();
     570                 :            : 
     571                 :            :             // write new is_last flag
     572                 :            :             write_start_of_chunk(tag,
     573                 :            :                                  start_of_chunk_header,
     574                 :            :                                  end_of_chunk_header,
     575                 :            :                                  true, // is_last_chunk
     576                 :            :                                  first_did_in_chunk,
     577         [ #  # ]:          0 :                                  last_did_in_chunk);
     578 [ #  # ][ #  # ]:          0 :             table->add(cursor->current_key, tag);
     579                 :            :         }
     580                 :            :     } else {
     581                 :            :         LOGLINE(DB, "PostlistChunkWriter::flush(): updating chunk which still has items in it");
     582                 :            :         /* The chunk still has some items in it.  Two major subcases:
     583                 :            :          * a) This is the first chunk.
     584                 :            :          * b) This isn't the first chunk.
     585                 :            :          *
     586                 :            :          * The subcases just affect the chunk header.
     587                 :            :          */
     588         [ +  - ]:     350535 :         string tag;
     589                 :            : 
     590                 :            :         /* First write the header, which depends on whether this is the
     591                 :            :          * first chunk.
     592                 :            :          */
     593         [ +  + ]:     350535 :         if (is_first_chunk) {
     594                 :            :             /* The first chunk.  This is the relatively easy case,
     595                 :            :              * and we just have to write this one back to disk.
     596                 :            :              */
     597                 :            :             LOGLINE(DB, "PostlistChunkWriter::flush(): rewriting the first chunk, which still has items in it");
     598         [ +  - ]:     343947 :             string key = GlassPostListTable::make_key(tname);
     599         [ +  - ]:     343947 :             bool ok = table->get_exact_entry(key, tag);
     600                 :            :             (void)ok;
     601                 :            :             Assert(ok);
     602                 :            :             Assert(!tag.empty());
     603                 :            : 
     604                 :            :             Xapian::doccount num_ent;
     605                 :            :             Xapian::termcount coll_freq;
     606                 :            :             {
     607                 :     343947 :                 const char * tagpos = tag.data();
     608                 :     343947 :                 const char * tagend = tagpos + tag.size();
     609                 :            :                 (void)read_start_of_first_chunk(&tagpos, tagend,
     610         [ +  - ]:     343947 :                                                 &num_ent, &coll_freq);
     611                 :            :             }
     612                 :            : 
     613 [ +  - ][ +  - ]:     343947 :             tag = make_start_of_first_chunk(num_ent, coll_freq, first_did);
     614                 :            : 
     615 [ +  - ][ +  - ]:     343947 :             tag += make_start_of_chunk(is_last_chunk, first_did, current_did);
     616         [ +  - ]:     343947 :             tag += chunk;
     617 [ +  - ][ +  - ]:     343947 :             table->add(key, tag);
     618                 :     343947 :             return;
     619                 :            :         }
     620                 :            : 
     621                 :            :         LOGLINE(DB, "PostlistChunkWriter::flush(): updating secondary chunk which still has items in it");
     622                 :            :         /* Not the first chunk.
     623                 :            :          *
     624                 :            :          * This has the easy sub-sub-case:
     625                 :            :          *   The first entry in the chunk hasn't changed
     626                 :            :          * ...and the hard sub-sub-case:
     627                 :            :          *   The first entry in the chunk has changed.  This is
     628                 :            :          *   harder because the key for the chunk changes, so
     629                 :            :          *   we've got to do a switch.
     630                 :            :          */
     631                 :            : 
     632                 :            :         // First find out the initial docid
     633                 :       6588 :         const char *keypos = orig_key.data();
     634                 :       6588 :         const char *keyend = keypos + orig_key.size();
     635 [ +  - ][ -  + ]:       6588 :         if (!check_tname_in_key(&keypos, keyend, tname)) {
     636 [ #  # ][ #  # ]:          0 :             throw Xapian::DatabaseCorruptError("Have invalid key writing to postlist");
                 [ #  # ]
     637                 :            :         }
     638                 :            :         Xapian::docid initial_did;
     639         [ -  + ]:       6588 :         if (!unpack_uint_preserving_sort(&keypos, keyend, &initial_did)) {
     640                 :          0 :             report_read_error(keypos);
     641                 :            :         }
     642 [ +  - ][ +  + ]:     357123 :         string new_key;
     643         [ -  + ]:       6588 :         if (initial_did != first_did) {
     644                 :            :             /* The fiddlier case:
     645                 :            :              * Create a new tag with the correct key, and replace
     646                 :            :              * the old one.
     647                 :            :              */
     648 [ #  # ][ #  # ]:          0 :             new_key = GlassPostListTable::make_key(tname, first_did);
     649         [ #  # ]:          0 :             table->del(orig_key);
     650                 :            :         } else {
     651         [ +  - ]:       6588 :             new_key = orig_key;
     652                 :            :         }
     653                 :            : 
     654                 :            :         // ...and write the start of this chunk.
     655 [ +  - ][ +  - ]:       6588 :         tag = make_start_of_chunk(is_last_chunk, first_did, current_did);
     656                 :            : 
     657         [ +  - ]:       6588 :         tag += chunk;
     658 [ +  - ][ +  - ]:     357163 :         table->add(new_key, tag);
     659                 :            :     }
     660                 :            : }
     661                 :            : 
     662                 :            : /** Read the number of entries in the posting list.
     663                 :            :  *  This must only be called when *posptr is pointing to the start of
     664                 :            :  *  the first chunk of the posting list.
     665                 :            :  */
     666                 :    1759599 : void GlassPostList::read_number_of_entries(const char ** posptr,
     667                 :            :                                    const char * end,
     668                 :            :                                    Xapian::doccount * number_of_entries_ptr,
     669                 :            :                                    Xapian::termcount * collection_freq_ptr)
     670                 :            : {
     671         [ -  + ]:    1759599 :     if (!unpack_uint(posptr, end, number_of_entries_ptr))
     672                 :          0 :         report_read_error(*posptr);
     673         [ -  + ]:    1759599 :     if (!unpack_uint(posptr, end, collection_freq_ptr))
     674                 :          0 :         report_read_error(*posptr);
     675                 :    1759599 : }
     676                 :            : 
     677                 :            : /** The format of a postlist is:
     678                 :            :  *
     679                 :            :  *  Split into chunks.  Key for first chunk is the termname (encoded as
     680                 :            :  *  length : name).  Key for subsequent chunks is the same, followed by the
     681                 :            :  *  document ID of the first document in the chunk (encoded as length of
     682                 :            :  *  representation in first byte, and then docid).
     683                 :            :  *
     684                 :            :  *  A chunk (except for the first chunk) contains:
     685                 :            :  *
     686                 :            :  *  1)  bool - true if this is the last chunk.
     687                 :            :  *  2)  difference between final docid in chunk and first docid.
     688                 :            :  *  3)  wdf for the first item.
     689                 :            :  *  4)  increment in docid to next item, followed by wdf for the item.
     690                 :            :  *  5)  (4) repeatedly.
     691                 :            :  *
     692                 :            :  *  The first chunk begins with the number of entries, the collection
     693                 :            :  *  frequency, then the docid of the first document, then has the header of a
     694                 :            :  *  standard chunk.
     695                 :            :  */
     696                 :     118495 : GlassPostList::GlassPostList(intrusive_ptr<const GlassDatabase> this_db_,
     697                 :            :                              const string & term_,
     698                 :            :                              bool keep_reference)
     699                 :            :         : LeafPostList(term_),
     700                 :            :           this_db(keep_reference ? this_db_ : NULL),
     701                 :            :           have_started(false),
     702                 :            :           is_at_end(false),
     703 [ +  + ][ +  + ]:     118495 :           cursor(this_db_->postlist_table.cursor_get())
     704                 :            : {
     705                 :            :     LOGCALL_CTOR(DB, "GlassPostList", this_db_.get() | term_ | keep_reference);
     706         [ +  - ]:     118483 :     init();
     707                 :     118483 : }
     708                 :            : 
     709                 :      99636 : GlassPostList::GlassPostList(intrusive_ptr<const GlassDatabase> this_db_,
     710                 :            :                              const string & term_,
     711                 :            :                              GlassCursor * cursor_)
     712                 :            :         : LeafPostList(term_),
     713                 :            :           this_db(this_db_),
     714                 :            :           have_started(false),
     715                 :            :           is_at_end(false),
     716                 :      99636 :           cursor(cursor_)
     717                 :            : {
     718                 :            :     LOGCALL_CTOR(DB, "GlassPostList", this_db_.get() | term_ | cursor_);
     719         [ +  - ]:      99636 :     init();
     720                 :      99636 : }
     721                 :            : 
     722                 :            : void
     723                 :     218119 : GlassPostList::init()
     724                 :            : {
     725         [ +  - ]:     218119 :     string key = GlassPostListTable::make_key(term);
     726         [ +  - ]:     218119 :     int found = cursor->find_entry(key);
     727         [ +  + ]:     218119 :     if (!found) {
     728                 :            :         LOGLINE(DB, "postlist for term not found");
     729                 :      25445 :         number_of_entries = 0;
     730                 :      25445 :         is_at_end = true;
     731                 :      25445 :         pos = 0;
     732                 :      25445 :         end = 0;
     733                 :      25445 :         first_did_in_chunk = 0;
     734                 :      25445 :         last_did_in_chunk = 0;
     735                 :     243564 :         return;
     736                 :            :     }
     737         [ +  - ]:     192674 :     cursor->read_tag();
     738                 :     192674 :     pos = cursor->current_tag.data();
     739                 :     192674 :     end = pos + cursor->current_tag.size();
     740                 :            : 
     741         [ +  - ]:     192674 :     did = read_start_of_first_chunk(&pos, end, &number_of_entries, NULL);
     742                 :     192674 :     first_did_in_chunk = did;
     743                 :            :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk,
     744         [ +  - ]:     192674 :                                             &is_last_chunk);
     745 [ +  - ][ +  + ]:     218119 :     read_wdf(&pos, end, &wdf);
     746                 :     218119 :     LOGLINE(DB, "Initial docid " << did);
     747                 :            : }
     748                 :            : 
     749                 :     654308 : GlassPostList::~GlassPostList()
     750                 :            : {
     751                 :            :     LOGCALL_DTOR(DB, "GlassPostList");
     752         [ +  + ]:     218119 :     delete positionlist;
     753         [ -  + ]:     436189 : }
     754                 :            : 
     755                 :            : LeafPostList *
     756                 :      99991 : GlassPostList::open_nearby_postlist(const std::string & term_,
     757                 :            :                                     bool need_read_pos) const
     758                 :            : {
     759                 :            :     LOGCALL(DB, LeafPostList *, "GlassPostList::open_nearby_postlist", term_ | need_read_pos);
     760                 :            :     (void)need_read_pos;
     761         [ -  + ]:      99991 :     if (term_.empty())
     762                 :          0 :         RETURN(NULL);
     763 [ +  - ][ +  + ]:      99991 :     if (!this_db.get() || this_db->postlist_table.is_writable())
                 [ +  + ]
     764                 :        355 :         RETURN(NULL);
     765 [ +  - ][ +  - ]:      99991 :     RETURN(new GlassPostList(this_db, term_, cursor->clone()));
                 [ +  - ]
     766                 :            : }
     767                 :            : 
     768                 :            : bool
     769                 :   28679486 : GlassPostList::next_in_chunk()
     770                 :            : {
     771                 :            :     LOGCALL(DB, bool, "GlassPostList::next_in_chunk", NO_ARGS);
     772         [ +  + ]:   28679486 :     if (pos == end) RETURN(false);
     773                 :            : 
     774                 :   28491440 :     read_did_increase(&pos, end, &did);
     775                 :   28491440 :     read_wdf(&pos, end, &wdf);
     776                 :            : 
     777                 :            :     // Either not at last doc in chunk, or pos == end, but not both.
     778                 :            :     Assert(did <= last_did_in_chunk);
     779                 :            :     Assert(did < last_did_in_chunk || pos == end);
     780                 :            :     Assert(pos != end || did == last_did_in_chunk);
     781                 :            : 
     782                 :   28491440 :     RETURN(true);
     783                 :            : }
     784                 :            : 
     785                 :            : void
     786                 :     188422 : GlassPostList::next_chunk()
     787                 :            : {
     788                 :            :     LOGCALL_VOID(DB, "GlassPostList::next_chunk", NO_ARGS);
     789         [ +  + ]:     188422 :     if (is_last_chunk) {
     790                 :     187641 :         is_at_end = true;
     791                 :     188422 :         return;
     792                 :            :     }
     793                 :            : 
     794         [ +  - ]:        781 :     cursor->next();
     795         [ -  + ]:        781 :     if (cursor->after_end()) {
     796                 :          0 :         is_at_end = true;
     797         [ #  # ]:          0 :         throw Xapian::DatabaseCorruptError("Unexpected end of posting list for '" +
     798 [ #  # ][ #  # ]:          0 :                                      term + "'");
                 [ #  # ]
     799                 :            :     }
     800                 :        781 :     const char * keypos = cursor->current_key.data();
     801                 :        781 :     const char * keyend = keypos + cursor->current_key.size();
     802                 :            :     // Check we're still in same postlist
     803 [ +  - ][ -  + ]:        781 :     if (!check_tname_in_key_lite(&keypos, keyend, term)) {
     804                 :          0 :         is_at_end = true;
     805         [ #  # ]:          0 :         throw Xapian::DatabaseCorruptError("Unexpected end of posting list for '" +
     806 [ #  # ][ #  # ]:          0 :                                      term + "'");
                 [ #  # ]
     807                 :            :     }
     808                 :            : 
     809                 :            :     Xapian::docid newdid;
     810         [ -  + ]:        781 :     if (!unpack_uint_preserving_sort(&keypos, keyend, &newdid)) {
     811                 :          0 :         report_read_error(keypos);
     812                 :            :     }
     813         [ -  + ]:        781 :     if (newdid <= did) {
     814 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseCorruptError("Document ID in new chunk of postlist (" +
     815         [ #  # ]:          0 :                 str(newdid) +
     816 [ #  # ][ #  # ]:          0 :                 ") is not greater than final document ID in previous chunk (" +
     817 [ #  # ][ #  # ]:          0 :                 str(did) + ")");
                 [ #  # ]
     818                 :            :     }
     819                 :        781 :     did = newdid;
     820                 :            : 
     821         [ +  - ]:        781 :     cursor->read_tag();
     822                 :        781 :     pos = cursor->current_tag.data();
     823                 :        781 :     end = pos + cursor->current_tag.size();
     824                 :            : 
     825                 :        781 :     first_did_in_chunk = did;
     826                 :            :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk,
     827         [ +  - ]:        781 :                                             &is_last_chunk);
     828         [ +  - ]:        781 :     read_wdf(&pos, end, &wdf);
     829                 :            : }
     830                 :            : 
     831                 :            : PositionList *
     832                 :       3294 : GlassPostList::read_position_list()
     833                 :            : {
     834                 :            :     LOGCALL(DB, PositionList *, "GlassPostList::read_position_list", NO_ARGS);
     835                 :            :     Assert(this_db.get());
     836         [ +  + ]:       3294 :     if (rare(positionlist == NULL)) {
     837                 :            :         // Lazily create positionlist to avoid the size cost for the common
     838                 :            :         // case where we don't want positional data.
     839         [ +  - ]:        964 :         positionlist = new GlassRePositionList(&this_db->position_table);
     840                 :            :     }
     841                 :       3294 :     this_db->read_position_list(positionlist, did, term);
     842                 :       3294 :     RETURN(positionlist);
     843                 :            : }
     844                 :            : 
     845                 :            : PositionList *
     846                 :      24495 : GlassPostList::open_position_list() const
     847                 :            : {
     848                 :            :     LOGCALL(DB, PositionList *, "GlassPostList::open_position_list", NO_ARGS);
     849                 :            :     Assert(this_db.get());
     850                 :      24495 :     RETURN(this_db->open_position_list(did, term));
     851                 :            : }
     852                 :            : 
     853                 :            : PostList *
     854                 :   28892115 : GlassPostList::next(double w_min)
     855                 :            : {
     856                 :            :     LOGCALL(DB, PostList *, "GlassPostList::next", w_min);
     857                 :            :     (void)w_min; // no warning
     858                 :            : 
     859         [ +  + ]:   28892115 :     if (!have_started) {
     860                 :     212629 :         have_started = true;
     861                 :            :     } else {
     862         [ +  + ]:   28679486 :         if (!next_in_chunk()) next_chunk();
     863                 :            :     }
     864                 :            : 
     865                 :   28892115 :     if (is_at_end) {
     866                 :            :         LOGLINE(DB, "Moved to end");
     867                 :            :     } else {
     868                 :            :         LOGLINE(DB, "Moved to docid " << did << ", wdf = " << wdf);
     869                 :            :     }
     870                 :            : 
     871                 :   28892115 :     RETURN(NULL);
     872                 :            : }
     873                 :            : 
     874                 :            : bool
     875                 :   28869638 : GlassPostList::current_chunk_contains(Xapian::docid desired_did)
     876                 :            : {
     877                 :            :     LOGCALL(DB, bool, "GlassPostList::current_chunk_contains", desired_did);
     878 [ +  + ][ +  + ]:   28869638 :     if (desired_did >= first_did_in_chunk &&
     879                 :   28861740 :         desired_did <= last_did_in_chunk) {
     880                 :   28856324 :         RETURN(true);
     881                 :            :     }
     882                 :      13314 :     RETURN(false);
     883                 :            : }
     884                 :            : 
     885                 :            : void
     886                 :     140144 : GlassPostList::move_to_chunk_containing(Xapian::docid desired_did)
     887                 :            : {
     888                 :            :     LOGCALL_VOID(DB, "GlassPostList::move_to_chunk_containing", desired_did);
     889 [ +  - ][ +  - ]:     140144 :     (void)cursor->find_entry(GlassPostListTable::make_key(term, desired_did));
     890                 :            :     Assert(!cursor->after_end());
     891                 :            : 
     892                 :     140144 :     const char * keypos = cursor->current_key.data();
     893                 :     140144 :     const char * keyend = keypos + cursor->current_key.size();
     894                 :            :     // Check we're still in same postlist
     895 [ +  - ][ -  + ]:     140144 :     if (!check_tname_in_key_lite(&keypos, keyend, term)) {
     896                 :            :         // This should only happen if the postlist doesn't exist at all.
     897                 :          0 :         is_at_end = true;
     898                 :          0 :         is_last_chunk = true;
     899                 :     140144 :         return;
     900                 :            :     }
     901                 :     140144 :     is_at_end = false;
     902                 :            : 
     903         [ +  - ]:     140144 :     cursor->read_tag();
     904                 :     140144 :     pos = cursor->current_tag.data();
     905                 :     140144 :     end = pos + cursor->current_tag.size();
     906                 :            : 
     907         [ +  + ]:     140144 :     if (keypos == keyend) {
     908                 :            :         // In first chunk
     909                 :            : #ifdef XAPIAN_ASSERTIONS
     910                 :            :         Xapian::doccount old_number_of_entries = number_of_entries;
     911                 :            :         did = read_start_of_first_chunk(&pos, end, &number_of_entries, NULL);
     912                 :            :         Assert(old_number_of_entries == number_of_entries);
     913                 :            : #else
     914         [ +  - ]:     131845 :         did = read_start_of_first_chunk(&pos, end, NULL, NULL);
     915                 :            : #endif
     916                 :            :     } else {
     917                 :            :         // In normal chunk
     918         [ -  + ]:       8299 :         if (!unpack_uint_preserving_sort(&keypos, keyend, &did)) {
     919                 :          0 :             report_read_error(keypos);
     920                 :            :         }
     921                 :            :     }
     922                 :            : 
     923                 :     140144 :     first_did_in_chunk = did;
     924                 :            :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk,
     925         [ +  - ]:     140144 :                                             &is_last_chunk);
     926         [ +  - ]:     140144 :     read_wdf(&pos, end, &wdf);
     927                 :            : 
     928                 :            :     // Possible, since desired_did might be after end of this chunk and before
     929                 :            :     // the next.
     930 [ +  + ][ +  - ]:     140144 :     if (desired_did > last_did_in_chunk) next_chunk();
     931                 :            : }
     932                 :            : 
     933                 :            : bool
     934                 :   28869278 : GlassPostList::move_forward_in_chunk_to_at_least(Xapian::docid desired_did)
     935                 :            : {
     936                 :            :     LOGCALL(DB, bool, "GlassPostList::move_forward_in_chunk_to_at_least", desired_did);
     937         [ +  + ]:   28869278 :     if (did >= desired_did)
     938                 :     113505 :         RETURN(true);
     939                 :            : 
     940         [ +  - ]:   28755773 :     if (desired_did <= last_did_in_chunk) {
     941         [ +  - ]:   46329608 :         while (pos != end) {
     942                 :   46329608 :             read_did_increase(&pos, end, &did);
     943         [ +  + ]:   46329608 :             if (did >= desired_did) {
     944                 :   28755773 :                 read_wdf(&pos, end, &wdf);
     945                 :   28755773 :                 RETURN(true);
     946                 :            :             }
     947                 :            :             // It's faster to just skip over the wdf than to decode it.
     948                 :   17573835 :             read_wdf(&pos, end, NULL);
     949                 :            :         }
     950                 :            : 
     951                 :            :         // If we hit the end of the chunk then last_did_in_chunk must be wrong.
     952                 :            :         Assert(false);
     953                 :            :     }
     954                 :            : 
     955                 :          0 :     pos = end;
     956                 :          0 :     RETURN(false);
     957                 :            : }
     958                 :            : 
     959                 :            : PostList *
     960                 :       8575 : GlassPostList::skip_to(Xapian::docid desired_did, double w_min)
     961                 :            : {
     962                 :            :     LOGCALL(DB, PostList *, "GlassPostList::skip_to", desired_did | w_min);
     963                 :            :     (void)w_min; // no warning
     964                 :            :     // We've started now - if we hadn't already, we're already positioned
     965                 :            :     // at start so there's no need to actually do anything.
     966                 :       8575 :     have_started = true;
     967                 :            : 
     968                 :            :     // Don't skip back, and don't need to do anything if already there.
     969 [ +  + ][ +  + ]:       8575 :     if (is_at_end || desired_did <= did) RETURN(NULL);
     970                 :            : 
     971                 :            :     // Move to correct chunk
     972         [ +  + ]:       7337 :     if (!current_chunk_contains(desired_did)) {
     973                 :        343 :         move_to_chunk_containing(desired_did);
     974                 :            :         // Might be at_end now, so we need to check before trying to move
     975                 :            :         // forward in chunk.
     976         [ +  - ]:        343 :         if (is_at_end) RETURN(NULL);
     977                 :            :     }
     978                 :            : 
     979                 :            :     // Move to correct position in chunk
     980                 :       6994 :     bool have_document = move_forward_in_chunk_to_at_least(desired_did);
     981                 :            :     (void)have_document;
     982                 :            :     Assert(have_document);
     983                 :            : 
     984                 :       6994 :     if (is_at_end) {
     985                 :            :         LOGLINE(DB, "Skipped to end");
     986                 :            :     } else {
     987                 :            :         LOGLINE(DB, "Skipped to docid " << did << ", wdf = " << wdf);
     988                 :            :     }
     989                 :            : 
     990                 :       6994 :     RETURN(NULL);
     991                 :            : }
     992                 :            : 
     993                 :            : // Used for doclens.
     994                 :            : bool
     995                 :   28886372 : GlassPostList::jump_to(Xapian::docid desired_did)
     996                 :            : {
     997                 :            :     LOGCALL(DB, bool, "GlassPostList::jump_to", desired_did);
     998                 :            :     // We've started now - if we hadn't already, we're already positioned
     999                 :            :     // at start so there's no need to actually do anything.
    1000                 :   28886372 :     have_started = true;
    1001                 :            : 
    1002                 :            :     // If the list is empty, give up right away.
    1003         [ +  + ]:   28886372 :     if (pos == 0) RETURN(false);
    1004                 :            : 
    1005                 :            :     // Move to correct chunk, or reload the current chunk to go backwards in it
    1006                 :            :     // (FIXME: perhaps handle the latter case more elegantly, though it won't
    1007                 :            :     // happen during sequential access which is most common).
    1008 [ +  + ][ +  + ]:   28862317 :     if (is_at_end || !current_chunk_contains(desired_did) || desired_did < did) {
         [ +  + ][ +  + ]
    1009                 :            :         // Clear is_at_end flag since we can rewind.
    1010                 :     139801 :         is_at_end = false;
    1011                 :            : 
    1012                 :     139801 :         move_to_chunk_containing(desired_did);
    1013                 :            :         // Might be at_end now, so we need to check before trying to move
    1014                 :            :         // forward in chunk.
    1015         [ +  + ]:     139801 :         if (is_at_end) RETURN(false);
    1016                 :            :     }
    1017                 :            : 
    1018                 :            :     // Move to correct position in chunk.
    1019         [ -  + ]:   28862284 :     if (!move_forward_in_chunk_to_at_least(desired_did)) RETURN(false);
    1020                 :   28862284 :     RETURN(desired_did == did);
    1021                 :            : }
    1022                 :            : 
    1023                 :            : string
    1024                 :         12 : GlassPostList::get_description() const
    1025                 :            : {
    1026                 :         12 :     string desc;
    1027         [ +  - ]:         12 :     description_append(desc, term);
    1028         [ +  - ]:         12 :     desc += ":";
    1029 [ +  - ][ +  - ]:         12 :     desc += str(number_of_entries);
    1030                 :         12 :     return desc;
    1031                 :            : }
    1032                 :            : 
    1033                 :            : // Returns the last did to allow in this chunk.
    1034                 :            : Xapian::docid
    1035                 :     350332 : GlassPostListTable::get_chunk(const string &tname,
    1036                 :            :                               Xapian::docid did, bool adding,
    1037                 :            :                               PostlistChunkReader ** from,
    1038                 :            :                               PostlistChunkWriter **to)
    1039                 :            : {
    1040                 :            :     LOGCALL(DB, Xapian::docid, "GlassPostListTable::get_chunk", tname | did | adding | from | to);
    1041                 :            :     // Get chunk containing entry
    1042         [ +  - ]:     350332 :     string key = make_key(tname, did);
    1043                 :            : 
    1044                 :            :     // Find the right chunk
    1045         [ +  - ]:     700664 :     unique_ptr<GlassCursor> cursor(cursor_get());
    1046                 :            : 
    1047         [ +  - ]:     350332 :     (void)cursor->find_entry(key);
    1048                 :            :     Assert(!cursor->after_end());
    1049                 :            : 
    1050                 :     350332 :     const char * keypos = cursor->current_key.data();
    1051                 :     350332 :     const char * keyend = keypos + cursor->current_key.size();
    1052                 :            : 
    1053 [ +  - ][ -  + ]:     350332 :     if (!check_tname_in_key(&keypos, keyend, tname)) {
    1054                 :            :         // Postlist for this termname doesn't exist.
    1055                 :            :         //
    1056                 :            :         // NB "adding" will only be true if we are adding, but it may sometimes
    1057                 :            :         // be false in some cases where we are actually adding.
    1058         [ #  # ]:          0 :         if (!adding)
    1059 [ #  # ][ #  # ]:          0 :             throw Xapian::DatabaseCorruptError("Attempted to delete or modify an entry in a non-existent posting list for " + tname);
                 [ #  # ]
    1060                 :            : 
    1061                 :          0 :         *from = NULL;
    1062 [ #  # ][ #  # ]:          0 :         *to = new PostlistChunkWriter(string(), true, tname, true);
                 [ #  # ]
    1063                 :          0 :         RETURN(Xapian::docid(-1));
    1064                 :            :     }
    1065                 :            : 
    1066                 :            :     // See if we're appending - if so we can shortcut by just copying
    1067                 :            :     // the data part of the chunk wholesale.
    1068                 :     350332 :     bool is_first_chunk = (keypos == keyend);
    1069                 :            :     LOGVALUE(DB, is_first_chunk);
    1070                 :            : 
    1071         [ +  - ]:     350332 :     cursor->read_tag();
    1072                 :     350332 :     const char * pos = cursor->current_tag.data();
    1073                 :     350332 :     const char * end = pos + cursor->current_tag.size();
    1074                 :            :     Xapian::docid first_did_in_chunk;
    1075         [ +  + ]:     350332 :     if (is_first_chunk) {
    1076         [ +  - ]:     343987 :         first_did_in_chunk = read_start_of_first_chunk(&pos, end, NULL, NULL);
    1077                 :            :     } else {
    1078         [ -  + ]:       6345 :         if (!unpack_uint_preserving_sort(&keypos, keyend, &first_did_in_chunk)) {
    1079                 :          0 :             report_read_error(keypos);
    1080                 :            :         }
    1081                 :            :     }
    1082                 :            : 
    1083                 :            :     bool is_last_chunk;
    1084                 :            :     Xapian::docid last_did_in_chunk;
    1085         [ +  - ]:     350332 :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk, &is_last_chunk);
    1086                 :     350332 :     *to = new PostlistChunkWriter(cursor->current_key, is_first_chunk, tname,
    1087 [ +  - ][ +  - ]:     350332 :                                   is_last_chunk);
    1088         [ +  + ]:     350332 :     if (did > last_did_in_chunk) {
    1089                 :            :         // This is the shortcut.  Not very pretty, but I'll leave refactoring
    1090                 :            :         // until I've a clearer picture of everything which needs to be done.
    1091                 :            :         // (FIXME)
    1092                 :     350152 :         *from = NULL;
    1093                 :            :         (*to)->raw_append(first_did_in_chunk, last_did_in_chunk,
    1094 [ +  - ][ +  - ]:     350152 :                           string(pos, end));
    1095                 :            :     } else {
    1096 [ +  - ][ +  - ]:        180 :         *from = new PostlistChunkReader(first_did_in_chunk, string(pos, end));
                 [ +  - ]
    1097                 :            :     }
    1098         [ +  + ]:     350332 :     if (is_last_chunk) RETURN(Xapian::docid(-1));
    1099                 :            : 
    1100                 :            :     // Find first did of next tag.
    1101         [ +  - ]:          8 :     cursor->next();
    1102         [ -  + ]:          8 :     if (cursor->after_end()) {
    1103 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseCorruptError("Expected another key but found none");
                 [ #  # ]
    1104                 :            :     }
    1105                 :          8 :     const char *kpos = cursor->current_key.data();
    1106                 :          8 :     const char *kend = kpos + cursor->current_key.size();
    1107 [ +  - ][ -  + ]:          8 :     if (!check_tname_in_key(&kpos, kend, tname)) {
    1108 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseCorruptError("Expected another key with the same term name but found a different one");
                 [ #  # ]
    1109                 :            :     }
    1110                 :            : 
    1111                 :            :     // Read the new first docid
    1112                 :            :     Xapian::docid first_did_of_next_chunk;
    1113         [ -  + ]:          8 :     if (!unpack_uint_preserving_sort(&kpos, kend, &first_did_of_next_chunk)) {
    1114                 :          0 :         report_read_error(kpos);
    1115                 :            :     }
    1116                 :     350340 :     RETURN(first_did_of_next_chunk - 1);
    1117                 :            : }
    1118                 :            : 
    1119                 :            : void
    1120                 :       9096 : GlassPostListTable::merge_doclen_changes(const map<Xapian::docid, Xapian::termcount> & doclens)
    1121                 :            : {
    1122                 :            :     LOGCALL_VOID(DB, "GlassPostListTable::merge_doclen_changes", doclens);
    1123                 :            : 
    1124                 :            :     // The cursor in the doclen_pl will no longer be valid, so reset it.
    1125                 :       9096 :     doclen_pl.reset(0);
    1126                 :            : 
    1127                 :            :     LOGVALUE(DB, doclens.size());
    1128         [ +  + ]:      18112 :     if (doclens.empty()) return;
    1129                 :            : 
    1130                 :            :     // Ensure there's a first chunk.
    1131 [ +  - ][ +  - ]:       9016 :     string current_key = make_key(string());
    1132 [ +  - ][ +  + ]:       9016 :     if (!key_exists(current_key)) {
    1133                 :            :         LOGLINE(DB, "Adding dummy first chunk");
    1134         [ +  - ]:        820 :         string newtag = make_start_of_first_chunk(0, 0, 0);
    1135 [ +  - ][ +  - ]:        820 :         newtag += make_start_of_chunk(true, 0, 0);
    1136 [ +  - ][ +  - ]:        820 :         add(current_key, newtag);
    1137                 :            :     }
    1138                 :            : 
    1139                 :       9016 :     map<Xapian::docid, Xapian::termcount>::const_iterator j;
    1140                 :       9016 :     j = doclens.begin();
    1141                 :            :     Assert(j != doclens.end()); // This case is caught above.
    1142                 :            : 
    1143                 :            :     Xapian::docid max_did;
    1144                 :            :     PostlistChunkReader *from;
    1145                 :            :     PostlistChunkWriter *to;
    1146 [ +  - ][ +  - ]:       9016 :     max_did = get_chunk(string(), j->first, true, &from, &to);
    1147                 :            :     LOGVALUE(DB, max_did);
    1148         [ +  + ]:     141705 :     for ( ; j != doclens.end(); ++j) {
    1149                 :     132689 :         Xapian::docid did = j->first;
    1150                 :            : 
    1151                 :            : next_doclen_chunk:
    1152                 :            :         LOGLINE(DB, "Updating doclens, did=" << did);
    1153 [ +  + ][ +  + ]:     148143 :         if (from) while (!from->is_at_end()) {
    1154                 :      15446 :             Xapian::docid copy_did = from->get_docid();
    1155         [ +  + ]:      15446 :             if (copy_did >= did) {
    1156 [ +  + ][ +  - ]:      12896 :                 if (copy_did == did) from->next();
    1157                 :      12896 :                 break;
    1158                 :            :             }
    1159         [ +  - ]:       2550 :             to->append(this, copy_did, from->get_wdf());
    1160         [ +  - ]:       2550 :             from->next();
    1161                 :            :         }
    1162 [ +  + ][ +  + ]:     132697 :         if ((!from || from->is_at_end()) && did > max_did) {
         [ +  + ][ +  + ]
    1163         [ +  - ]:          8 :             delete from;
    1164         [ +  - ]:          8 :             to->flush(this);
    1165         [ +  - ]:          8 :             delete to;
    1166 [ +  - ][ +  - ]:          8 :             max_did = get_chunk(string(), did, false, &from, &to);
    1167                 :          8 :             goto next_doclen_chunk;
    1168                 :            :         }
    1169                 :            : 
    1170                 :     132689 :         Xapian::termcount new_doclen = j->second;
    1171         [ +  + ]:     132689 :         if (new_doclen != static_cast<Xapian::termcount>(-1)) {
    1172         [ +  - ]:     119782 :             to->append(this, did, new_doclen);
    1173                 :            :         }
    1174                 :            :     }
    1175                 :            : 
    1176         [ +  + ]:       9016 :     if (from) {
    1177         [ +  + ]:        600 :         while (!from->is_at_end()) {
    1178         [ +  - ]:        517 :             to->append(this, from->get_docid(), from->get_wdf());
    1179         [ +  - ]:        517 :             from->next();
    1180                 :            :         }
    1181         [ +  - ]:         83 :         delete from;
    1182                 :            :     }
    1183         [ +  - ]:       9016 :     to->flush(this);
    1184         [ +  - ]:       9016 :     delete to;
    1185                 :            : }
    1186                 :            : 
    1187                 :            : void
    1188                 :     341507 : GlassPostListTable::merge_changes(const string &term,
    1189                 :            :                                   const Inverter::PostingChanges & changes)
    1190                 :            : {
    1191                 :            :     {
    1192                 :            :         // Rewrite the first chunk of this posting list with the updated
    1193                 :            :         // termfreq and collfreq.
    1194         [ +  - ]:     341507 :         string current_key = make_key(term);
    1195 [ +  - ][ +  + ]:     683014 :         string tag;
    1196         [ +  - ]:     341507 :         (void)get_exact_entry(current_key, tag);
    1197                 :            : 
    1198                 :            :         // Read start of first chunk to get termfreq and collfreq.
    1199                 :     341507 :         const char *pos = tag.data();
    1200                 :     341507 :         const char *end = pos + tag.size();
    1201                 :            :         Xapian::doccount termfreq;
    1202                 :            :         Xapian::termcount collfreq;
    1203                 :            :         Xapian::docid firstdid, lastdid;
    1204                 :            :         bool islast;
    1205         [ +  + ]:     341507 :         if (pos == end) {
    1206                 :     173154 :             termfreq = 0;
    1207                 :     173154 :             collfreq = 0;
    1208                 :     173154 :             firstdid = 0;
    1209                 :     173154 :             lastdid = 0;
    1210                 :     173154 :             islast = true;
    1211                 :            :         } else {
    1212                 :            :             firstdid = read_start_of_first_chunk(&pos, end,
    1213         [ +  - ]:     168353 :                                                  &termfreq, &collfreq);
    1214                 :            :             // Handle the generic start of chunk header.
    1215         [ +  - ]:     168353 :             lastdid = read_start_of_chunk(&pos, end, firstdid, &islast);
    1216                 :            :         }
    1217                 :            : 
    1218                 :     341507 :         termfreq += changes.get_tfdelta();
    1219         [ +  + ]:     341507 :         if (termfreq == 0) {
    1220                 :            :             // All postings deleted!  So we can shortcut by zapping the
    1221                 :            :             // posting list.
    1222         [ +  + ]:        189 :             if (islast) {
    1223                 :            :                 // Only one entry for this posting list.
    1224         [ +  - ]:        179 :                 del(current_key);
    1225                 :        179 :                 return;
    1226                 :            :             }
    1227         [ +  - ]:         10 :             MutableGlassCursor cursor(this);
    1228         [ +  - ]:         10 :             bool found = cursor.find_entry(current_key);
    1229                 :            :             Assert(found);
    1230         [ -  + ]:         10 :             if (!found) return; // Reduce damage!
    1231 [ +  - ][ +  + ]:         26 :             while (cursor.del()) {
    1232                 :         21 :                 const char *kpos = cursor.current_key.data();
    1233                 :         21 :                 const char *kend = kpos + cursor.current_key.size();
    1234 [ +  - ][ +  + ]:         21 :                 if (!check_tname_in_key_lite(&kpos, kend, term)) break;
    1235                 :            :             }
    1236                 :        189 :             return;
    1237                 :            :         }
    1238                 :     341318 :         collfreq += changes.get_cfdelta();
    1239                 :            : 
    1240                 :            :         // Rewrite start of first chunk to update termfreq and collfreq.
    1241 [ +  - ][ +  + ]:     682825 :         string newhdr = make_start_of_first_chunk(termfreq, collfreq, firstdid);
    1242 [ +  - ][ +  - ]:     341318 :         newhdr += make_start_of_chunk(islast, firstdid, lastdid);
    1243         [ +  + ]:     341318 :         if (pos == end) {
    1244 [ +  - ][ +  + ]:     173085 :             add(current_key, newhdr);
    1245                 :            :         } else {
    1246                 :            :             Assert((size_t)(pos - tag.data()) <= tag.size());
    1247         [ +  - ]:     168233 :             tag.replace(0, pos - tag.data(), newhdr);
    1248 [ +  - ][ +  - ]:     168233 :             add(current_key, tag);
    1249                 :     341318 :         }
    1250                 :            :     }
    1251                 :     341308 :     map<Xapian::docid, Xapian::termcount>::const_iterator j;
    1252                 :     341308 :     j = changes.pl_changes.begin();
    1253                 :            :     Assert(j != changes.pl_changes.end()); // This case is caught above.
    1254                 :            : 
    1255                 :            :     Xapian::docid max_did;
    1256                 :            :     PostlistChunkReader *from;
    1257                 :            :     PostlistChunkWriter *to;
    1258         [ +  - ]:     341497 :     max_did = get_chunk(term, j->first, false, &from, &to);
    1259         [ +  + ]:    2376928 :     for ( ; j != changes.pl_changes.end(); ++j) {
    1260                 :    2035620 :         Xapian::docid did = j->first;
    1261                 :            : 
    1262                 :            : next_chunk:
    1263                 :            :         LOGLINE(DB, "Updating term=" << term << ", did=" << did);
    1264 [ +  + ][ +  - ]:    2045943 :         if (from) while (!from->is_at_end()) {
    1265                 :      10323 :             Xapian::docid copy_did = from->get_docid();
    1266         [ +  + ]:      10323 :             if (copy_did >= did) {
    1267         [ +  + ]:        129 :                 if (copy_did == did) {
    1268         [ +  - ]:        126 :                     from->next();
    1269                 :            :                 }
    1270                 :        129 :                 break;
    1271                 :            :             }
    1272         [ +  - ]:      10194 :             to->append(this, copy_did, from->get_wdf());
    1273         [ +  - ]:      10194 :             from->next();
    1274                 :            :         }
    1275 [ +  + ][ +  + ]:    2035620 :         if ((!from || from->is_at_end()) && did > max_did) {
         [ -  + ][ -  + ]
    1276         [ #  # ]:          0 :             delete from;
    1277         [ #  # ]:          0 :             to->flush(this);
    1278         [ #  # ]:          0 :             delete to;
    1279         [ #  # ]:          0 :             max_did = get_chunk(term, did, false, &from, &to);
    1280                 :          0 :             goto next_chunk;
    1281                 :            :         }
    1282                 :            : 
    1283                 :    2035620 :         Xapian::termcount new_wdf = j->second;
    1284         [ +  + ]:    2035620 :         if (new_wdf != Xapian::termcount(-1)) {
    1285         [ +  - ]:    2035388 :             to->append(this, did, new_wdf);
    1286                 :            :         }
    1287                 :            :     }
    1288                 :            : 
    1289         [ +  + ]:     341308 :     if (from) {
    1290         [ +  + ]:       2132 :         while (!from->is_at_end()) {
    1291         [ +  - ]:       2043 :             to->append(this, from->get_docid(), from->get_wdf());
    1292         [ +  - ]:       2043 :             from->next();
    1293                 :            :         }
    1294         [ +  - ]:         89 :         delete from;
    1295                 :            :     }
    1296         [ +  - ]:     341308 :     to->flush(this);
    1297         [ +  - ]:     341497 :     delete to;
    1298                 :            : }
    1299                 :            : 
    1300                 :            : void
    1301                 :         37 : GlassPostListTable::get_used_docid_range(Xapian::docid & first,
    1302                 :            :                                          Xapian::docid & last) const
    1303                 :            : {
    1304                 :            :     LOGCALL(DB, Xapian::docid, "GlassPostListTable::get_used_docid_range", "&first, &used");
    1305         [ +  - ]:         37 :     unique_ptr<GlassCursor> cur(cursor_get());
    1306 [ +  - ][ +  - ]:         37 :     if (!cur->find_entry(pack_glass_postlist_key(string()))) {
         [ +  - ][ -  + ]
    1307                 :            :         // Empty database.
    1308                 :          0 :         first = last = 0;
    1309                 :          0 :         return;
    1310                 :            :     }
    1311                 :            : 
    1312         [ +  - ]:         37 :     cur->read_tag();
    1313                 :         37 :     const char * p = cur->current_tag.data();
    1314                 :         37 :     const char * e = p + cur->current_tag.size();
    1315                 :            : 
    1316         [ +  - ]:         37 :     first = read_start_of_first_chunk(&p, e, NULL, NULL);
    1317                 :            : 
    1318 [ +  - ][ +  - ]:         37 :     (void)cur->find_entry(pack_glass_postlist_key(string(), GLASS_MAX_DOCID));
                 [ +  - ]
    1319                 :            :     Assert(!cur->after_end());
    1320                 :            : 
    1321                 :         37 :     const char * keypos = cur->current_key.data();
    1322                 :         37 :     const char * keyend = keypos + cur->current_key.size();
    1323                 :            :     // Check we're still in same postlist
    1324 [ +  - ][ +  - ]:         37 :     if (!check_tname_in_key_lite(&keypos, keyend, string())) {
                 [ -  + ]
    1325                 :            :         // Shouldn't happen - we already handled the empty database case above.
    1326                 :            :         Assert(false);
    1327                 :          0 :         first = last = 0;
    1328                 :          0 :         return;
    1329                 :            :     }
    1330                 :            : 
    1331         [ +  - ]:         37 :     cur->read_tag();
    1332                 :         37 :     p = cur->current_tag.data();
    1333                 :         37 :     e = p + cur->current_tag.size();
    1334                 :            : 
    1335                 :            :     Xapian::docid start_of_last_chunk;
    1336         [ +  - ]:         37 :     if (keypos == keyend) {
    1337                 :         37 :         start_of_last_chunk = first;
    1338         [ +  - ]:         37 :         first = read_start_of_first_chunk(&p, e, NULL, NULL);
    1339                 :            :     } else {
    1340                 :            :         // In normal chunk
    1341         [ #  # ]:          0 :         if (!unpack_uint_preserving_sort(&keypos, keyend,
    1342                 :          0 :                                          &start_of_last_chunk)) {
    1343                 :          0 :             report_read_error(keypos);
    1344                 :            :         }
    1345                 :            :     }
    1346                 :            : 
    1347                 :            :     bool dummy;
    1348 [ +  - ][ +  - ]:         37 :     last = read_start_of_chunk(&p, e, start_of_last_chunk, &dummy);
    1349                 :            : }
    1350                 :            : 
    1351                 :            : #ifdef DISABLE_GPL_LIBXAPIAN
    1352                 :            : # error GPL source we cannot relicense included in libxapian
    1353                 :            : #endif

Generated by: LCOV version 1.11