LCOV - code coverage report
Current view: top level - backends/flint - flint_postlist.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core r Lines: 305 411 74.2 %
Date: 2011-08-21 Functions: 42 46 91.3 %
Branches: 114 192 59.4 %

           Branch data     Line data    Source code
       1                 :            : /* flint_postlist.cc: Postlists in a flint database
       2                 :            :  *
       3                 :            :  * Copyright 1999,2000,2001 BrightStation PLC
       4                 :            :  * Copyright 2002,2003,2004,2005,2007,2008,2009 Olly Betts
       5                 :            :  * Copyright 2007,2009 Lemur Consulting Ltd
       6                 :            :  *
       7                 :            :  * This program is free software; you can redistribute it and/or
       8                 :            :  * modify it under the terms of the GNU General Public License as
       9                 :            :  * published by the Free Software Foundation; either version 2 of the
      10                 :            :  * License, or (at your option) any later version.
      11                 :            :  *
      12                 :            :  * This program is distributed in the hope that it will be useful,
      13                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      14                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15                 :            :  * GNU General Public License for more details.
      16                 :            :  *
      17                 :            :  * You should have received a copy of the GNU General Public License
      18                 :            :  * along with this program; if not, write to the Free Software
      19                 :            :  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
      20                 :            :  * USA
      21                 :            :  */
      22                 :            : 
      23                 :            : #include <config.h>
      24                 :            : #include "flint_postlist.h"
      25                 :            : 
      26                 :            : #include "flint_cursor.h"
      27                 :            : #include "flint_database.h"
      28                 :            : #include "flint_utils.h"
      29                 :            : #include "debuglog.h"
      30                 :            : #include "noreturn.h"
      31                 :            : #include "str.h"
      32                 :            : 
      33                 :            : Xapian::doccount
      34                 :     194241 : FlintPostListTable::get_termfreq(const string & term) const
      35                 :            : {
      36                 :     194241 :     string key = make_key(term);
      37                 :     194241 :     string tag;
      38         [ +  + ]:     194241 :     if (!get_exact_entry(key, tag)) return 0;
      39                 :            : 
      40                 :            :     Xapian::doccount termfreq;
      41                 :     169887 :     const char * p = tag.data();
      42                 :     169887 :     FlintPostList::read_number_of_entries(&p, p + tag.size(), &termfreq, NULL);
      43                 :     194242 :     return termfreq;
      44                 :            : }
      45                 :            : 
      46                 :            : Xapian::termcount
      47                 :     236913 : FlintPostListTable::get_collection_freq(const string & term) const
      48                 :            : {
      49                 :     236913 :     string key = make_key(term);
      50                 :     236913 :     string tag;
      51         [ +  + ]:     236913 :     if (!get_exact_entry(key, tag)) return 0;
      52                 :            : 
      53                 :            :     Xapian::termcount collfreq;
      54                 :     189661 :     const char * p = tag.data();
      55                 :     189661 :     FlintPostList::read_number_of_entries(&p, p + tag.size(), NULL, &collfreq);
      56                 :     236913 :     return collfreq;
      57                 :            : }
      58                 :            : 
      59                 :            : // How big should chunks in the posting list be?  (They
      60                 :            : // will grow slightly bigger than this, but not more than a
      61                 :            : // few bytes extra) - FIXME: tune this value to try to
      62                 :            : // maximise how well blocks are used.  Or performance.
      63                 :            : // Or indexing speed.  Or something...
      64                 :            : const unsigned int CHUNKSIZE = 2000;
      65                 :            : 
      66                 :            : /** FlintPostlistChunkWriter is a wrapper which acts roughly as an
      67                 :            :  *  output iterator on a postlist chunk, taking care of the
      68                 :            :  *  messy details.  It's intended to be used with deletion and
      69                 :            :  *  replacing of entries, not for adding to the end, when it's
      70                 :            :  *  not really needed.
      71                 :            :  */
      72                 :      21513 : class FlintPostlistChunkWriter {
      73                 :            :     public:
      74                 :            :         FlintPostlistChunkWriter(const string &orig_key_,
      75                 :            :                             bool is_first_chunk_,
      76                 :            :                             const string &tname_,
      77                 :            :                             bool is_last_chunk_);
      78                 :            : 
      79                 :            :         /// Append an entry to this chunk.
      80                 :            :         void append(FlintTable * table, Xapian::docid did,
      81                 :            :                     Xapian::termcount wdf, flint_doclen_t doclen);
      82                 :            : 
      83                 :            :         /// Append a block of raw entries to this chunk.
      84                 :      21362 :         void raw_append(Xapian::docid first_did_, Xapian::docid current_did_,
      85                 :            :                         const string & s) {
      86                 :            :             Assert(!started);
      87                 :      21362 :             first_did = first_did_;
      88                 :      21362 :             current_did = current_did_;
      89         [ +  + ]:      21362 :             if (!s.empty()) {
      90                 :        266 :                 chunk.append(s);
      91                 :        266 :                 started = true;
      92                 :            :             }
      93                 :      21362 :         }
      94                 :            : 
      95                 :            :         /** Flush the chunk to the buffered table.  Note: this may write it
      96                 :            :          *  with a different key to the original one, if for example the first
      97                 :            :          *  entry has changed.
      98                 :            :          */
      99                 :            :         void flush(FlintTable *table);
     100                 :            : 
     101                 :            :     private:
     102                 :            :         string orig_key;
     103                 :            :         string tname;
     104                 :            :         bool is_first_chunk;
     105                 :            :         bool is_last_chunk;
     106                 :            :         bool started;
     107                 :            : 
     108                 :            :         Xapian::docid first_did;
     109                 :            :         Xapian::docid current_did;
     110                 :            : 
     111                 :            :         string chunk;
     112                 :            : };
     113                 :            : 
     114                 :            : // Static functions
     115                 :            : 
     116                 :            : /// Report an error when reading the posting list.
     117                 :            : XAPIAN_NORETURN(static void report_read_error(const char * position));
     118                 :          0 : static void report_read_error(const char * position)
     119                 :            : {
     120         [ #  # ]:          0 :     if (position == 0) {
     121                 :            :         // data ran out
     122                 :          0 :         throw Xapian::DatabaseCorruptError("Data ran out unexpectedly when reading posting list.");
     123                 :            :     }
     124                 :            :     // overflow
     125                 :          0 :     throw Xapian::RangeError("Value in posting list too large.");
     126                 :            : }
     127                 :            : 
     128                 :      22694 : static inline bool get_tname_from_key(const char **src, const char *end,
     129                 :            :                                string &tname)
     130                 :            : {
     131                 :      22694 :     return F_unpack_string_preserving_sort(src, end, tname);
     132                 :            : }
     133                 :            : 
     134                 :            : static inline bool
     135                 :      22694 : check_tname_in_key_lite(const char **keypos, const char *keyend, const string &tname)
     136                 :            : {
     137                 :      22694 :     string tname_in_key;
     138                 :            : 
     139                 :            :     // Read the termname.
     140         [ -  + ]:      22694 :     if (!get_tname_from_key(keypos, keyend, tname_in_key)) {
     141                 :          0 :         report_read_error(*keypos);
     142                 :            :     }
     143                 :            : 
     144                 :            :     // This should only fail if the postlist doesn't exist at all.
     145                 :      22694 :     return tname_in_key == tname;
     146                 :            : }
     147                 :            : 
     148                 :            : static inline bool
     149                 :      22336 : check_tname_in_key(const char **keypos, const char *keyend, const string &tname)
     150                 :            : {
     151         [ -  + ]:      22336 :     if (*keypos == keyend) return false;
     152                 :            : 
     153                 :      22336 :     return check_tname_in_key_lite(keypos, keyend, tname);
     154                 :            : }
     155                 :            : 
     156                 :            : /// Read the start of the first chunk in the posting list.
     157                 :            : static Xapian::docid
     158                 :     163767 : read_start_of_first_chunk(const char ** posptr,
     159                 :            :                           const char * end,
     160                 :            :                           Xapian::doccount * number_of_entries_ptr,
     161                 :            :                           Xapian::termcount * collection_freq_ptr)
     162                 :            : {
     163                 :            :     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);
     164                 :            : 
     165                 :            :     FlintPostList::read_number_of_entries(posptr, end,
     166                 :     163767 :                            number_of_entries_ptr, collection_freq_ptr);
     167                 :            :     if (number_of_entries_ptr)
     168                 :            :         LOGVALUE(DB, *number_of_entries_ptr);
     169                 :            :     if (collection_freq_ptr)
     170                 :            :         LOGVALUE(DB, *collection_freq_ptr);
     171                 :            : 
     172                 :            :     Xapian::docid did;
     173                 :            :     // Read the docid of the first entry in the posting list.
     174         [ -  + ]:     163767 :     if (!F_unpack_uint(posptr, end, &did))
     175                 :          0 :         report_read_error(*posptr);
     176                 :     163767 :     ++did;
     177                 :            :     LOGVALUE(DB, did);
     178                 :     163767 :     RETURN(did);
     179                 :            : }
     180                 :            : 
     181                 :   18772454 : static inline void read_did_increase(const char ** posptr,
     182                 :            :                               const char * end,
     183                 :            :                               Xapian::docid * did_ptr)
     184                 :            : {
     185                 :            :     Xapian::docid did_increase;
     186         [ -  + ]:   18772454 :     if (!F_unpack_uint(posptr, end, &did_increase)) report_read_error(*posptr);
     187                 :   18772454 :     *did_ptr += did_increase + 1;
     188                 :   18772454 : }
     189                 :            : 
     190                 :            : /// Read the wdf and the document length of an item.
     191                 :   18893071 : static inline void read_wdf_and_length(const char ** posptr,
     192                 :            :                                 const char * end,
     193                 :            :                                 Xapian::termcount * wdf_ptr,
     194                 :            :                                 flint_doclen_t * doclength_ptr)
     195                 :            : {
     196         [ -  + ]:   18893071 :     if (!F_unpack_uint(posptr, end, wdf_ptr)) report_read_error(*posptr);
     197         [ -  + ]:   18893071 :     if (!F_unpack_uint(posptr, end, doclength_ptr)) report_read_error(*posptr);
     198                 :   18893071 : }
     199                 :            : 
     200                 :            : /// Read the start of a chunk.
     201                 :            : static Xapian::docid
     202                 :     142479 : read_start_of_chunk(const char ** posptr,
     203                 :            :                     const char * end,
     204                 :            :                     Xapian::docid first_did_in_chunk,
     205                 :            :                     bool * is_last_chunk_ptr)
     206                 :            : {
     207                 :            :     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));
     208                 :            : 
     209                 :            :     // Read whether this is the last chunk
     210         [ -  + ]:     142479 :     if (!F_unpack_bool(posptr, end, is_last_chunk_ptr))
     211                 :          0 :         report_read_error(*posptr);
     212                 :            :     if (is_last_chunk_ptr)
     213                 :            :         LOGVALUE(DB, *is_last_chunk_ptr);
     214                 :            : 
     215                 :            :     // Read what the final document ID in this chunk is.
     216                 :            :     Xapian::docid increase_to_last;
     217         [ -  + ]:     142479 :     if (!F_unpack_uint(posptr, end, &increase_to_last))
     218                 :          0 :         report_read_error(*posptr);
     219                 :     142479 :     ++increase_to_last;
     220                 :     142479 :     Xapian::docid last_did_in_chunk = first_did_in_chunk + increase_to_last;
     221                 :            :     LOGVALUE(DB, last_did_in_chunk);
     222                 :     142479 :     RETURN(last_did_in_chunk);
     223                 :            : }
     224                 :            : 
     225                 :     784724 : static string make_wdf_and_length(Xapian::termcount wdf, flint_doclen_t doclength)
     226                 :            : {
     227                 :     784724 :     return F_pack_uint(wdf) + F_pack_uint(doclength);
     228                 :            : }
     229                 :            : 
     230                 :          0 : static void write_start_of_chunk(string & chunk,
     231                 :            :                                  unsigned int start_of_chunk_header,
     232                 :            :                                  unsigned int end_of_chunk_header,
     233                 :            :                                  bool is_last_chunk,
     234                 :            :                                  Xapian::docid first_did_in_chunk,
     235                 :            :                                  Xapian::docid last_did_in_chunk)
     236                 :            : {
     237                 :            :     Assert((size_t)(end_of_chunk_header - start_of_chunk_header) <= chunk.size());
     238                 :            :     Assert(last_did_in_chunk >= first_did_in_chunk);
     239                 :          0 :     Xapian::docid increase_to_last = last_did_in_chunk - first_did_in_chunk;
     240                 :            : 
     241                 :            :     chunk.replace(start_of_chunk_header,
     242                 :            :                   end_of_chunk_header - start_of_chunk_header,
     243                 :          0 :                   F_pack_bool(is_last_chunk) + F_pack_uint(increase_to_last - 1));
     244                 :            :     // FIXME - storing increase_to_last - 1 is bogus as this value is
     245                 :            :     // -1 when a postlist chunk has a single entry!  Luckily the code
     246                 :            :     // works despite this, but it's ugly.
     247                 :          0 : }
     248                 :            : 
     249                 :            : /** FlintPostlistChunkReader is essentially an iterator wrapper
     250                 :            :  *  around a postlist chunk.  It simply iterates through the
     251                 :            :  *  entries in a postlist.
     252                 :            :  */
     253                 :        151 : class FlintPostlistChunkReader {
     254                 :            :     public:
     255                 :            :         /** Initialise the postlist chunk reader.
     256                 :            :          *
     257                 :            :          *  @param first_did  First document id in this chunk.
     258                 :            :          *  @param data       The tag string with the header removed.
     259                 :            :          */
     260                 :        151 :         FlintPostlistChunkReader(Xapian::docid first_did, const string & data_)
     261                 :        151 :             : data(data_), pos(data.data()), end(pos + data.length()), at_end(data.empty()), did(first_did)
     262                 :            :         {
     263         [ +  - ]:        151 :             if (!at_end) read_wdf_and_length(&pos, end, &wdf, &doclength);
     264                 :        151 :         }
     265                 :            : 
     266                 :       8336 :         Xapian::docid get_docid() const {
     267                 :       8336 :             return did;
     268                 :            :         }
     269                 :       8153 :         Xapian::termcount get_wdf() const {
     270                 :       8153 :             return wdf;
     271                 :            :         }
     272                 :       8153 :         flint_doclen_t get_doclength() const {
     273                 :            :             LOGCALL(DB, flint_doclen_t, "FlintPostlistChunkReader::get_doclength", NO_ARGS);
     274                 :       8153 :             RETURN(doclength);
     275                 :            :         }
     276                 :            : 
     277                 :       8670 :         bool is_at_end() const {
     278                 :       8670 :             return at_end;
     279                 :            :         }
     280                 :            : 
     281                 :            :         /** Advance to the next entry.  Set at_end if we run off the end.
     282                 :            :          */
     283                 :            :         void next();
     284                 :            : 
     285                 :            :     private:
     286                 :            :         string data;
     287                 :            : 
     288                 :            :         const char *pos;
     289                 :            :         const char *end;
     290                 :            : 
     291                 :            :         bool at_end;
     292                 :            : 
     293                 :            :         Xapian::docid did;
     294                 :            :         Xapian::termcount wdf;
     295                 :            :         flint_doclen_t doclength;
     296                 :            : };
     297                 :            : 
     298                 :            : void
     299                 :       8333 : FlintPostlistChunkReader::next()
     300                 :            : {
     301         [ +  + ]:       8333 :     if (pos == end) {
     302                 :        151 :         at_end = true;
     303                 :            :     } else {
     304                 :       8182 :         read_did_increase(&pos, end, &did);
     305                 :       8182 :         read_wdf_and_length(&pos, end, &wdf, &doclength);
     306                 :            :     }
     307                 :       8333 : }
     308                 :            : 
     309                 :      21513 : FlintPostlistChunkWriter::FlintPostlistChunkWriter(const string &orig_key_,
     310                 :            :                                          bool is_first_chunk_,
     311                 :            :                                          const string &tname_,
     312                 :            :                                          bool is_last_chunk_)
     313                 :            :         : orig_key(orig_key_),
     314                 :            :           tname(tname_), is_first_chunk(is_first_chunk_),
     315                 :            :           is_last_chunk(is_last_chunk_),
     316                 :      21513 :           started(false)
     317                 :            : {
     318                 :            :     LOGCALL_VOID(DB, "FlintPostlistChunkWriter::FlintPostlistChunkWriter", orig_key_ | is_first_chunk_ | tname_ | is_last_chunk_);
     319                 :      21513 : }
     320                 :            : 
     321                 :            : void
     322                 :     784724 : FlintPostlistChunkWriter::append(FlintTable * table, Xapian::docid did,
     323                 :            :                             Xapian::termcount wdf, flint_doclen_t doclen)
     324                 :            : {
     325         [ +  + ]:     784724 :     if (!started) {
     326                 :      21247 :         started = true;
     327                 :      21247 :         first_did = did;
     328                 :            :     } else {
     329                 :            :         Assert(did > current_did);
     330                 :            :         // Start a new chunk if this one has grown to the threshold.
     331         [ +  + ]:     763477 :         if (chunk.size() >= CHUNKSIZE) {
     332                 :        811 :             bool save_is_last_chunk = is_last_chunk;
     333                 :        811 :             is_last_chunk = false;
     334                 :        811 :             flush(table);
     335                 :        811 :             is_last_chunk = save_is_last_chunk;
     336                 :        811 :             is_first_chunk = false;
     337                 :        811 :             first_did = did;
     338                 :        811 :             chunk.resize(0);
     339                 :        811 :             orig_key = FlintPostListTable::make_key(tname, first_did);
     340                 :            :         } else {
     341                 :     762666 :             chunk.append(F_pack_uint(did - current_did - 1));
     342                 :            :         }
     343                 :            :     }
     344                 :     784724 :     current_did = did;
     345                 :     784724 :     chunk.append(make_wdf_and_length(wdf, doclen));
     346                 :     784724 : }
     347                 :            : 
     348                 :            : /** Make the data to go at the start of the very first chunk.
     349                 :            :  */
     350                 :            : static inline string
     351                 :      43022 : make_start_of_first_chunk(Xapian::doccount entries,
     352                 :            :                           Xapian::termcount collectionfreq,
     353                 :            :                           Xapian::docid new_did)
     354                 :            : {
     355                 :      43022 :     return F_pack_uint(entries) + F_pack_uint(collectionfreq) + F_pack_uint(new_did - 1);
     356                 :            : }
     357                 :            : 
     358                 :            : /** Make the data to go at the start of a standard chunk.
     359                 :            :  */
     360                 :            : static inline string
     361                 :      43845 : make_start_of_chunk(bool new_is_last_chunk,
     362                 :            :                     Xapian::docid new_first_did,
     363                 :            :                     Xapian::docid new_final_did)
     364                 :            : {
     365                 :            :     Assert(new_final_did >= new_first_did);
     366                 :            :     return F_pack_bool(new_is_last_chunk) +
     367                 :      43845 :             F_pack_uint(new_final_did - new_first_did - 1);
     368                 :            : }
     369                 :            : 
     370                 :            : void
     371                 :      22324 : FlintPostlistChunkWriter::flush(FlintTable *table)
     372                 :            : {
     373                 :            :     LOGCALL_VOID(DB, "FlintPostlistChunkWriter::flush", table);
     374                 :            : 
     375                 :            :     /* This is one of the more messy parts involved with updating posting
     376                 :            :      * list chunks.
     377                 :            :      * 
     378                 :            :      * Depending on circumstances, we may have to delete an entire chunk
     379                 :            :      * or file it under a different key, as well as possibly modifying both
     380                 :            :      * the previous and next chunk of the postlist.
     381                 :            :      */
     382                 :            : 
     383         [ -  + ]:      22324 :     if (!started) {
     384                 :            :         /* This chunk is now empty so disappears entirely.
     385                 :            :          *
     386                 :            :          * If this was the last chunk, then the previous chunk
     387                 :            :          * must have its "is_last_chunk" flag updated.
     388                 :            :          *
     389                 :            :          * If this was the first chunk, then the next chunk must
     390                 :            :          * be transformed into the first chunk.  Messy!
     391                 :            :          */
     392                 :            :         LOGLINE(DB, "FlintPostlistChunkWriter::flush(): deleting chunk");
     393                 :            :         Assert(!orig_key.empty());
     394         [ #  # ]:          0 :         if (is_first_chunk) {
     395                 :            :             LOGLINE(DB, "FlintPostlistChunkWriter::flush(): deleting first chunk");
     396         [ #  # ]:          0 :             if (is_last_chunk) {
     397                 :            :                 /* This is the first and the last chunk, ie the only
     398                 :            :                  * chunk, so just delete the tag.
     399                 :            :                  */
     400                 :          0 :                 table->del(orig_key);
     401                 :          0 :                 return;
     402                 :            :             }
     403                 :            : 
     404                 :            :             /* This is the messiest case.  The first chunk is to
     405                 :            :              * be removed, and there is at least one chunk after
     406                 :            :              * it.  Need to rewrite the next chunk as the first
     407                 :            :              * chunk.
     408                 :            :              */
     409                 :          0 :             AutoPtr<FlintCursor> cursor(table->cursor_get());
     410                 :            : 
     411         [ #  # ]:          0 :             if (!cursor->find_entry(orig_key)) {
     412                 :          0 :                 throw Xapian::DatabaseCorruptError("The key we're working on has disappeared");
     413                 :            :             }
     414                 :            : 
     415                 :            :             // Extract existing counts from the first chunk so we can reinsert
     416                 :            :             // them into the block we're renaming.
     417                 :            :             Xapian::doccount num_ent;
     418                 :            :             Xapian::termcount coll_freq;
     419                 :            :             {
     420                 :          0 :                 cursor->read_tag();
     421                 :          0 :                 const char *tagpos = cursor->current_tag.data();
     422                 :          0 :                 const char *tagend = tagpos + cursor->current_tag.size();
     423                 :            : 
     424                 :            :                 (void)read_start_of_first_chunk(&tagpos, tagend,
     425                 :          0 :                                                 &num_ent, &coll_freq);
     426                 :            :             }
     427                 :            : 
     428                 :            :             // Seek to the next chunk.
     429                 :          0 :             cursor->next();
     430         [ #  # ]:          0 :             if (cursor->after_end()) {
     431                 :          0 :                 throw Xapian::DatabaseCorruptError("Expected another key but found none");
     432                 :            :             }
     433                 :          0 :             const char *kpos = cursor->current_key.data();
     434                 :          0 :             const char *kend = kpos + cursor->current_key.size();
     435         [ #  # ]:          0 :             if (!check_tname_in_key(&kpos, kend, tname)) {
     436                 :          0 :                 throw Xapian::DatabaseCorruptError("Expected another key with the same term name but found a different one");
     437                 :            :             }
     438                 :            : 
     439                 :            :             // Read the new first docid
     440                 :            :             Xapian::docid new_first_did;
     441         [ #  # ]:          0 :             if (!F_unpack_uint_preserving_sort(&kpos, kend, &new_first_did)) {
     442                 :          0 :                 report_read_error(kpos);
     443                 :            :             }
     444                 :            : 
     445                 :          0 :             cursor->read_tag();
     446                 :          0 :             const char *tagpos = cursor->current_tag.data();
     447                 :          0 :             const char *tagend = tagpos + cursor->current_tag.size();
     448                 :            : 
     449                 :            :             // Read the chunk header
     450                 :            :             bool new_is_last_chunk;
     451                 :            :             Xapian::docid new_last_did_in_chunk =
     452                 :            :                 read_start_of_chunk(&tagpos, tagend, new_first_did,
     453                 :          0 :                                     &new_is_last_chunk);
     454                 :            : 
     455                 :          0 :             string chunk_data(tagpos, tagend);
     456                 :            : 
     457                 :            :             // First remove the renamed tag
     458                 :          0 :             table->del(cursor->current_key);
     459                 :            : 
     460                 :            :             // And now write it as the first chunk
     461                 :          0 :             string tag;
     462                 :          0 :             tag = make_start_of_first_chunk(num_ent, coll_freq, new_first_did);
     463                 :            :             tag += make_start_of_chunk(new_is_last_chunk,
     464                 :            :                                               new_first_did,
     465                 :          0 :                                               new_last_did_in_chunk);
     466                 :          0 :             tag += chunk_data;
     467                 :          0 :             table->add(orig_key, tag);
     468                 :          0 :             return;
     469                 :            :         }
     470                 :            : 
     471                 :            :         LOGLINE(DB, "FlintPostlistChunkWriter::flush(): deleting secondary chunk");
     472                 :            :         /* This isn't the first chunk.  Check whether we're the last
     473                 :            :          * chunk.
     474                 :            :          */
     475                 :            : 
     476                 :            :         // Delete this chunk
     477                 :          0 :         table->del(orig_key);
     478                 :            : 
     479         [ #  # ]:          0 :         if (is_last_chunk) {
     480                 :            :             LOGLINE(DB, "FlintPostlistChunkWriter::flush(): deleting secondary last chunk");
     481                 :            :             // Update the previous chunk's is_last_chunk flag.
     482                 :          0 :             AutoPtr<FlintCursor> cursor(table->cursor_get());
     483                 :            : 
     484                 :            :             /* Should not find the key we just deleted, but should
     485                 :            :              * find the previous chunk. */
     486         [ #  # ]:          0 :             if (cursor->find_entry(orig_key)) {
     487                 :          0 :                 throw Xapian::DatabaseCorruptError("Flint key not deleted as we expected");
     488                 :            :             }
     489                 :            :             // Make sure this is a chunk with the right term attached.
     490                 :          0 :             const char * keypos = cursor->current_key.data();
     491                 :          0 :             const char * keyend = keypos + cursor->current_key.size();
     492         [ #  # ]:          0 :             if (!check_tname_in_key(&keypos, keyend, tname)) {
     493                 :          0 :                 throw Xapian::DatabaseCorruptError("Couldn't find chunk before delete chunk");
     494                 :            :             }
     495                 :            : 
     496                 :          0 :             bool is_prev_first_chunk = (keypos == keyend);
     497                 :            : 
     498                 :            :             // Now update the last_chunk
     499                 :          0 :             cursor->read_tag();
     500                 :          0 :             string tag = cursor->current_tag;
     501                 :            : 
     502                 :          0 :             const char *tagpos = tag.data();
     503                 :          0 :             const char *tagend = tagpos + tag.size();
     504                 :            : 
     505                 :            :             // Skip first chunk header
     506                 :            :             Xapian::docid first_did_in_chunk;
     507         [ #  # ]:          0 :             if (is_prev_first_chunk) {
     508                 :            :                 first_did_in_chunk = read_start_of_first_chunk(&tagpos, tagend,
     509                 :          0 :                                                                0, 0);
     510                 :            :             } else {
     511         [ #  # ]:          0 :                 if (!F_unpack_uint_preserving_sort(&keypos, keyend,
     512                 :            :                                                    &first_did_in_chunk))
     513                 :          0 :                     report_read_error(keypos);
     514                 :            :             }
     515                 :            :             bool wrong_is_last_chunk;
     516                 :          0 :             string::size_type start_of_chunk_header = tagpos - tag.data();
     517                 :            :             Xapian::docid last_did_in_chunk =
     518                 :            :                 read_start_of_chunk(&tagpos, tagend, first_did_in_chunk,
     519                 :          0 :                                     &wrong_is_last_chunk);
     520                 :          0 :             string::size_type end_of_chunk_header = tagpos - tag.data();
     521                 :            : 
     522                 :            :             // write new is_last flag
     523                 :            :             write_start_of_chunk(tag,
     524                 :            :                                  start_of_chunk_header,
     525                 :            :                                  end_of_chunk_header,
     526                 :            :                                  true, // is_last_chunk
     527                 :            :                                  first_did_in_chunk,
     528                 :          0 :                                  last_did_in_chunk);
     529                 :          0 :             table->add(cursor->current_key, tag);
     530                 :            :         }
     531                 :            :     } else {
     532                 :            :         LOGLINE(DB, "FlintPostlistChunkWriter::flush(): updating chunk which still has items in it");
     533                 :            :         /* The chunk still has some items in it.  Two major subcases:
     534                 :            :          * a) This is the first chunk.
     535                 :            :          * b) This isn't the first chunk.
     536                 :            :          *
     537                 :            :          * The subcases just affect the chunk header.
     538                 :            :          */
     539                 :      22324 :         string tag;
     540                 :            : 
     541                 :            :         /* First write the header, which depends on whether this is the
     542                 :            :          * first chunk.
     543                 :            :          */
     544         [ +  + ]:      22324 :         if (is_first_chunk) {
     545                 :            :             /* The first chunk.  This is the relatively easy case,
     546                 :            :              * and we just have to write this one back to disk.
     547                 :            :              */
     548                 :            :             LOGLINE(DB, "FlintPostlistChunkWriter::flush(): rewriting the first chunk, which still has items in it");
     549                 :      21501 :             string key = FlintPostListTable::make_key(tname);
     550                 :      21501 :             bool ok = table->get_exact_entry(key, tag);
     551                 :            :             (void)ok;
     552                 :            :             Assert(ok);
     553                 :            :             Assert(!tag.empty());
     554                 :            : 
     555                 :            :             Xapian::doccount num_ent;
     556                 :            :             Xapian::termcount coll_freq;
     557                 :            :             {
     558                 :      21501 :                 const char * tagpos = tag.data();
     559                 :      21501 :                 const char * tagend = tagpos + tag.size();
     560                 :            :                 (void)read_start_of_first_chunk(&tagpos, tagend,
     561                 :      21501 :                                                 &num_ent, &coll_freq);
     562                 :            :             }
     563                 :            : 
     564                 :      21501 :             tag = make_start_of_first_chunk(num_ent, coll_freq, first_did);
     565                 :            : 
     566                 :      21501 :             tag += make_start_of_chunk(is_last_chunk, first_did, current_did);
     567                 :      21501 :             tag += chunk;
     568                 :      21501 :             table->add(key, tag);
     569                 :      21501 :             return;
     570                 :            :         }
     571                 :            : 
     572                 :            :         LOGLINE(DB, "FlintPostlistChunkWriter::flush(): updating secondary chunk which still has items in it");
     573                 :            :         /* Not the first chunk.
     574                 :            :          *
     575                 :            :          * This has the easy sub-sub-case:
     576                 :            :          *   The first entry in the chunk hasn't changed
     577                 :            :          * ...and the hard sub-sub-case:
     578                 :            :          *   The first entry in the chunk has changed.  This is
     579                 :            :          *   harder because the key for the chunk changes, so
     580                 :            :          *   we've got to do a switch.
     581                 :            :          */
     582                 :            : 
     583                 :            :         // First find out the initial docid
     584                 :        823 :         const char *keypos = orig_key.data();
     585                 :        823 :         const char *keyend = keypos + orig_key.size();
     586         [ -  + ]:        823 :         if (!check_tname_in_key(&keypos, keyend, tname)) {
     587                 :          0 :             throw Xapian::DatabaseCorruptError("Have invalid key writing to postlist");
     588                 :            :         }
     589                 :            :         Xapian::docid initial_did;
     590         [ -  + ]:        823 :         if (!F_unpack_uint_preserving_sort(&keypos, keyend, &initial_did)) {
     591                 :          0 :             report_read_error(keypos);
     592                 :            :         }
     593                 :        823 :         string new_key;
     594         [ -  + ]:        823 :         if (initial_did != first_did) {
     595                 :            :             /* The fiddlier case:
     596                 :            :              * Create a new tag with the correct key, and replace
     597                 :            :              * the old one.
     598                 :            :              */
     599                 :          0 :             new_key = FlintPostListTable::make_key(tname, first_did);
     600                 :          0 :             table->del(orig_key);
     601                 :            :         } else {
     602                 :        823 :             new_key = orig_key;
     603                 :            :         }
     604                 :            : 
     605                 :            :         // ...and write the start of this chunk.
     606                 :        823 :         tag = make_start_of_chunk(is_last_chunk, first_did, current_did);
     607                 :            : 
     608                 :        823 :         tag += chunk;
     609                 :      22324 :         table->add(new_key, tag);
     610                 :            :     }
     611                 :            : }
     612                 :            : 
     613                 :            : /** Read the number of entries in the posting list.
     614                 :            :  *  This must only be called when *posptr is pointing to the start of
     615                 :            :  *  the first chunk of the posting list.
     616                 :            :  */
     617                 :     524313 : void FlintPostList::read_number_of_entries(const char ** posptr,
     618                 :            :                                    const char * end,
     619                 :            :                                    Xapian::doccount * number_of_entries_ptr,
     620                 :            :                                    Xapian::termcount * collection_freq_ptr)
     621                 :            : {
     622         [ -  + ]:     524313 :     if (!F_unpack_uint(posptr, end, number_of_entries_ptr))
     623                 :          0 :         report_read_error(*posptr);
     624         [ -  + ]:     524313 :     if (!F_unpack_uint(posptr, end, collection_freq_ptr))
     625                 :          0 :         report_read_error(*posptr);
     626                 :     524313 : }
     627                 :            : 
     628                 :            : /** The format of a postlist is:
     629                 :            :  *
     630                 :            :  *  Split into chunks.  Key for first chunk is the termname (encoded as
     631                 :            :  *  length : name).  Key for subsequent chunks is the same, followed by the
     632                 :            :  *  document ID of the first document in the chunk (encoded as length of
     633                 :            :  *  representation in first byte, and then docid).
     634                 :            :  *
     635                 :            :  *  A chunk (except for the first chunk) contains:
     636                 :            :  *
     637                 :            :  *  1)  bool - true if this is the last chunk.
     638                 :            :  *  2)  difference between final docid in chunk and first docid.
     639                 :            :  *  3)  wdf, then doclength of first item.
     640                 :            :  *  4)  increment in docid to next item, followed by wdf and doclength of item
     641                 :            :  *  5)  (4) repeatedly.
     642                 :            :  *
     643                 :            :  *  The first chunk begins with the number of entries, the collection
     644                 :            :  *  frequency, then the docid of the first document, then has the header of a
     645                 :            :  *  standard chunk.
     646                 :            :  */
     647                 :     144182 : FlintPostList::FlintPostList(Xapian::Internal::RefCntPtr<const FlintDatabase> this_db_,
     648                 :            :                              const string & term_)
     649                 :            :         : LeafPostList(term_),
     650                 :            :           this_db(this_db_),
     651                 :            :           have_started(false),
     652                 :            :           cursor(this_db->postlist_table.cursor_get()),
     653                 :     144182 :           is_at_end(false)
     654                 :            : {
     655                 :            :     LOGCALL_VOID(DB, "FlintPostList::FlintPostList", this_db_.get() | term_);
     656                 :     144177 :     string key = FlintPostListTable::make_key(term);
     657                 :     144177 :     int found = cursor->find_entry(key);
     658   [ +  +  +  + ]:     144177 :     if (!found) {
     659                 :      24035 :         number_of_entries = 0;
     660                 :      24035 :         is_at_end = true;
     661                 :      24035 :         pos = 0;
     662                 :      24035 :         end = 0;
     663                 :      24035 :         first_did_in_chunk = 0;
     664                 :      24035 :         last_did_in_chunk = 0;
     665                 :            :         return;
     666                 :            :     }
     667                 :     120142 :     cursor->read_tag();
     668                 :     120142 :     pos = cursor->current_tag.data();
     669                 :     120142 :     end = pos + cursor->current_tag.size();
     670                 :            : 
     671                 :     120142 :     did = read_start_of_first_chunk(&pos, end, &number_of_entries, NULL);
     672                 :     120142 :     first_did_in_chunk = did;
     673                 :            :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk,
     674                 :     120142 :                                             &is_last_chunk);
     675                 :     144177 :     read_wdf_and_length(&pos, end, &wdf, &doclength);
     676                 :     144192 : }
     677                 :            : 
     678                 :     144177 : FlintPostList::~FlintPostList()
     679                 :            : {
     680                 :            :     LOGCALL_VOID(DB, "FlintPostList::~FlintPostList", NO_ARGS);
     681 [ +  - ][ #  # ]:     144177 : }
                 [ -  + ]
     682                 :            : 
     683                 :            : bool
     684                 :   18882349 : FlintPostList::next_in_chunk()
     685                 :            : {
     686                 :            :     LOGCALL(DB, bool, "FlintPostList::next_in_chunk", NO_ARGS);
     687         [ +  + ]:   18882349 :     if (pos == end) RETURN(false);
     688                 :            : 
     689                 :   18764272 :     read_did_increase(&pos, end, &did);
     690                 :   18764272 :     read_wdf_and_length(&pos, end, &wdf, &doclength);
     691                 :            : 
     692                 :            :     // Either not at last doc in chunk, or pos == end, but not both.
     693                 :            :     Assert(did <= last_did_in_chunk);
     694                 :            :     Assert(did < last_did_in_chunk || pos == end);
     695                 :            :     Assert(pos != end || did == last_did_in_chunk);
     696                 :            : 
     697                 :   18882349 :     RETURN(true);
     698                 :            : }
     699                 :            : 
     700                 :            : void
     701                 :     118200 : FlintPostList::next_chunk()
     702                 :            : {
     703                 :            :     LOGCALL_VOID(DB, "FlintPostList::next_chunk", NO_ARGS);
     704         [ +  + ]:     118200 :     if (is_last_chunk) {
     705                 :     117999 :         is_at_end = true;
     706                 :     117999 :         return;
     707                 :            :     }
     708                 :            : 
     709                 :        201 :     cursor->next();
     710         [ -  + ]:        201 :     if (cursor->after_end()) {
     711                 :          0 :         is_at_end = true;
     712                 :            :         throw Xapian::DatabaseCorruptError("Unexpected end of posting list for `" +
     713                 :          0 :                                      term + "'");
     714                 :            :     }
     715                 :        201 :     const char * keypos = cursor->current_key.data();
     716                 :        201 :     const char * keyend = keypos + cursor->current_key.size();
     717                 :            :     // Check we're still in same postlist
     718         [ -  + ]:        201 :     if (!check_tname_in_key_lite(&keypos, keyend, term)) {
     719                 :          0 :         is_at_end = true;
     720                 :            :         throw Xapian::DatabaseCorruptError("Unexpected end of posting list for `" +
     721                 :          0 :                                      term + "'");
     722                 :            :     }
     723                 :            : 
     724                 :            :     Xapian::docid newdid;
     725         [ -  + ]:        201 :     if (!F_unpack_uint_preserving_sort(&keypos, keyend, &newdid)) {
     726                 :          0 :         report_read_error(keypos);
     727                 :            :     }
     728         [ -  + ]:        201 :     if (newdid <= did) {
     729                 :            :         throw Xapian::DatabaseCorruptError("Document ID in new chunk of postlist (" +
     730                 :            :                 str(newdid) +
     731                 :            :                 ") is not greater than final document ID in previous chunk (" +
     732                 :          0 :                 str(did) + ")");
     733                 :            :     }
     734                 :        201 :     did = newdid;
     735                 :            : 
     736                 :        201 :     cursor->read_tag();
     737                 :        201 :     pos = cursor->current_tag.data();
     738                 :        201 :     end = pos + cursor->current_tag.size();
     739                 :            : 
     740                 :        201 :     first_did_in_chunk = did;
     741                 :            :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk,
     742                 :        201 :                                             &is_last_chunk);
     743                 :     118200 :     read_wdf_and_length(&pos, end, &wdf, &doclength);
     744                 :            : }
     745                 :            : 
     746                 :            : Xapian::termcount
     747                 :   18869800 : FlintPostList::get_doclength() const
     748                 :            : {
     749                 :            :     LOGCALL(DB, Xapian::termcount, "FlintPostList::get_doclength", NO_ARGS);
     750                 :            :     Assert(have_started);
     751                 :   18869800 :     RETURN(static_cast<Xapian::termcount>(doclength));
     752                 :            : }
     753                 :            : 
     754                 :            : PositionList *
     755                 :       2281 : FlintPostList::read_position_list()
     756                 :            : {
     757                 :            :     LOGCALL(DB, PositionList *, "FlintPostList::read_position_list", NO_ARGS);
     758                 :       2281 :     positionlist.read_data(&this_db->position_table, did, term);
     759                 :       2281 :     RETURN(&positionlist);
     760                 :            : }
     761                 :            : 
     762                 :            : PositionList *
     763                 :      11391 : FlintPostList::open_position_list() const
     764                 :            : {
     765                 :            :     LOGCALL(DB, PositionList *, "FlintPostList::open_position_list", NO_ARGS);
     766                 :      11391 :     RETURN(new FlintPositionList(&this_db->position_table, did, term));
     767                 :            : }
     768                 :            : 
     769                 :            : PostList *
     770                 :   19019500 : FlintPostList::next(Xapian::weight w_min)
     771                 :            : {
     772                 :            :     LOGCALL(DB, PostList *, "FlintPostList::next", w_min);
     773                 :            :     (void)w_min; // no warning
     774                 :            : 
     775         [ +  + ]:   19019500 :     if (!have_started) {
     776                 :     141899 :         have_started = true;
     777                 :            :     } else {
     778         [ +  + ]:   18877601 :         if (!next_in_chunk()) next_chunk();
     779                 :            :     }
     780                 :            : 
     781                 :   19019500 :     if (is_at_end) {
     782                 :            :         LOGLINE(DB, "Moved to end");
     783                 :            :     } else {
     784                 :            :         LOGLINE(DB, "Moved to docid " << did << ", wdf = " << wdf <<
     785                 :            :                 ", doclength = " << doclength);
     786                 :            :     }
     787                 :            : 
     788                 :   19019500 :     RETURN(NULL);
     789                 :            : }
     790                 :            : 
     791                 :            : bool
     792                 :       2569 : FlintPostList::current_chunk_contains(Xapian::docid desired_did)
     793                 :            : {
     794                 :            :     LOGCALL(DB, bool, "FlintPostList::current_chunk_contains", desired_did);
     795 [ +  - ][ +  + ]:       2569 :     if (desired_did >= first_did_in_chunk &&
     796                 :            :         desired_did <= last_did_in_chunk) {
     797                 :       2446 :         RETURN(true);
     798                 :            :     }
     799                 :       2569 :     RETURN(false);
     800                 :            : }
     801                 :            : 
     802                 :            : void
     803                 :        123 : FlintPostList::move_to_chunk_containing(Xapian::docid desired_did)
     804                 :            : {
     805                 :            :     LOGCALL_VOID(DB, "FlintPostList::move_to_chunk_containing", desired_did);
     806                 :        123 :     (void)cursor->find_entry(FlintPostListTable::make_key(term, desired_did));
     807                 :            :     Assert(!cursor->after_end());
     808                 :            : 
     809                 :        123 :     const char * keypos = cursor->current_key.data();
     810                 :        123 :     const char * keyend = keypos + cursor->current_key.size();
     811                 :            :     // Check we're still in same postlist
     812         [ -  + ]:        123 :     if (!check_tname_in_key_lite(&keypos, keyend, term)) {
     813                 :            :         // This should only happen if the postlist doesn't exist at all.
     814                 :          0 :         is_at_end = true;
     815                 :          0 :         is_last_chunk = true;
     816                 :          0 :         return;
     817                 :            :     }
     818                 :        123 :     is_at_end = false;
     819                 :            : 
     820                 :        123 :     cursor->read_tag();
     821                 :        123 :     pos = cursor->current_tag.data();
     822                 :        123 :     end = pos + cursor->current_tag.size();
     823                 :            : 
     824         [ +  - ]:        123 :     if (keypos == keyend) {
     825                 :            :         // In first chunk
     826                 :            : #ifdef XAPIAN_ASSERTIONS
     827                 :            :         Xapian::doccount old_number_of_entries = number_of_entries;
     828                 :            :         did = read_start_of_first_chunk(&pos, end, &number_of_entries, NULL);
     829                 :            :         Assert(old_number_of_entries == number_of_entries);
     830                 :            : #else
     831                 :        123 :         did = read_start_of_first_chunk(&pos, end, NULL, NULL);
     832                 :            : #endif
     833                 :            :     } else {
     834                 :            :         // In normal chunk
     835         [ #  # ]:          0 :         if (!F_unpack_uint_preserving_sort(&keypos, keyend, &did)) {
     836                 :          0 :             report_read_error(keypos);
     837                 :            :         }
     838                 :            :     }
     839                 :            : 
     840                 :        123 :     first_did_in_chunk = did;
     841                 :            :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk,
     842                 :        123 :                                             &is_last_chunk);
     843                 :        123 :     read_wdf_and_length(&pos, end, &wdf, &doclength);
     844                 :            : 
     845                 :            :     // Possible, since desired_did might be after end of this chunk and before
     846                 :            :     // the next.
     847         [ +  - ]:        123 :     if (desired_did > last_did_in_chunk) next_chunk();
     848                 :            : }
     849                 :            : 
     850                 :            : bool
     851                 :       2446 : FlintPostList::move_forward_in_chunk_to_at_least(Xapian::docid desired_did)
     852                 :            : {
     853                 :            :     LOGCALL(DB, bool, "FlintPostList::move_forward_in_chunk_to_at_least", desired_did);
     854         [ -  + ]:       2446 :     if (desired_did > last_did_in_chunk) {
     855                 :          0 :         pos = end;
     856                 :          0 :         RETURN(false);
     857                 :            :     }
     858         [ +  + ]:       7194 :     while (did < desired_did) {
     859                 :            :         // FIXME: perhaps we don't need to decode the wdf and document length
     860                 :            :         // for documents we're skipping past.
     861                 :       4748 :         bool at_end_of_chunk = !next_in_chunk();
     862         [ -  + ]:       4748 :         if (at_end_of_chunk) RETURN(false);
     863                 :            :     }
     864                 :       2446 :     RETURN(true);
     865                 :            : }
     866                 :            : 
     867                 :            : PostList *
     868                 :       3118 : FlintPostList::skip_to(Xapian::docid desired_did, Xapian::weight w_min)
     869                 :            : {
     870                 :            :     LOGCALL(DB, PostList *, "FlintPostList::skip_to", desired_did | w_min);
     871                 :            :     (void)w_min; // no warning
     872                 :            :     // We've started now - if we hadn't already, we're already positioned
     873                 :            :     // at start so there's no need to actually do anything.
     874                 :       3118 :     have_started = true;
     875                 :            : 
     876                 :            :     // Don't skip back, and don't need to do anything if already there.
     877 [ +  + ][ +  + ]:       3118 :     if (is_at_end || desired_did <= did) RETURN(NULL);
     878                 :            : 
     879                 :            :     // Move to correct chunk
     880         [ +  + ]:       2569 :     if (!current_chunk_contains(desired_did)) {
     881                 :        123 :         move_to_chunk_containing(desired_did);
     882                 :            :         // Might be at_end now, so we need to check before trying to move
     883                 :            :         // forward in chunk.
     884         [ +  - ]:        123 :         if (is_at_end) RETURN(NULL);
     885                 :            :     }
     886                 :            : 
     887                 :            :     // Move to correct position in chunk
     888                 :       2446 :     bool have_document = move_forward_in_chunk_to_at_least(desired_did);
     889                 :            :     (void)have_document;
     890                 :            :     Assert(have_document);
     891                 :            : 
     892                 :       2446 :     if (is_at_end) {
     893                 :            :         LOGLINE(DB, "Skipped to end");
     894                 :            :     } else {
     895                 :            :         LOGLINE(DB, "Skipped to docid " << did << ", wdf = " << wdf <<
     896                 :            :                 ", doclength = " << doclength);
     897                 :            :     }
     898                 :            : 
     899                 :       3118 :     RETURN(NULL);
     900                 :            : }
     901                 :            : 
     902                 :            : string
     903                 :          9 : FlintPostList::get_description() const
     904                 :            : {
     905                 :          9 :     return term + ":" + str(number_of_entries);
     906                 :            : }
     907                 :            : 
     908                 :            : // Returns the last did to allow in this chunk.
     909                 :            : Xapian::docid
     910                 :      21513 : FlintPostListTable::get_chunk(const string &tname,
     911                 :            :           Xapian::docid did, bool adding,
     912                 :            :           FlintPostlistChunkReader ** from, FlintPostlistChunkWriter **to)
     913                 :            : {
     914                 :            :     // Get chunk containing entry
     915                 :      21513 :     string key = make_key(tname, did);
     916                 :            : 
     917                 :            :     // Find the right chunk
     918                 :      21513 :     AutoPtr<FlintCursor> cursor(cursor_get());
     919                 :            : 
     920                 :      21513 :     cursor->find_entry(key);
     921                 :            :     Assert(!cursor->after_end());
     922                 :            : 
     923                 :      21513 :     const char * keypos = cursor->current_key.data();
     924                 :      21513 :     const char * keyend = keypos + cursor->current_key.size();
     925                 :            : 
     926         [ -  + ]:      21513 :     if (!check_tname_in_key(&keypos, keyend, tname)) {
     927                 :            :         // Postlist for this termname doesn't exist.
     928         [ #  # ]:          0 :         if (!adding)
     929                 :          0 :             throw Xapian::DatabaseCorruptError("Attempted to delete or modify an entry in a non-existent posting list for " + tname);
     930                 :            : 
     931                 :          0 :         *from = NULL;
     932                 :          0 :         *to = new FlintPostlistChunkWriter(string(), true, tname, true);
     933                 :          0 :         return Xapian::docid(-1);
     934                 :            :     }
     935                 :            : 
     936                 :            :     // See if we're appending - if so we can shortcut by just copying
     937                 :            :     // the data part of the chunk wholesale.
     938                 :      21513 :     bool is_first_chunk = (keypos == keyend);
     939                 :            : 
     940                 :      21513 :     cursor->read_tag();
     941                 :      21513 :     const char * pos = cursor->current_tag.data();
     942                 :      21513 :     const char * end = pos + cursor->current_tag.size();
     943                 :            :     Xapian::docid first_did_in_chunk;
     944         [ +  + ]:      21513 :     if (is_first_chunk) {
     945                 :      21501 :         first_did_in_chunk = read_start_of_first_chunk(&pos, end, NULL, NULL);
     946                 :            :     } else {
     947         [ -  + ]:         12 :         if (!F_unpack_uint_preserving_sort(&keypos, keyend,
     948                 :            :                                            &first_did_in_chunk)) {
     949                 :          0 :             report_read_error(keypos);
     950                 :            :         }
     951                 :            :     }
     952                 :            : 
     953                 :            :     bool is_last_chunk;
     954                 :            :     Xapian::docid last_did_in_chunk;
     955                 :      21513 :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk, &is_last_chunk);
     956                 :            :     *to = new FlintPostlistChunkWriter(cursor->current_key, is_first_chunk, tname,
     957                 :      21513 :                                   is_last_chunk);
     958         [ +  + ]:      21513 :     if (did > last_did_in_chunk) {
     959                 :            :         // This is the shortcut.  Not very pretty, but I'll leave refactoring
     960                 :            :         // until I've a clearer picture of everything which needs to be done.
     961                 :            :         // (FIXME)
     962                 :      21362 :         *from = NULL;
     963                 :            :         (*to)->raw_append(first_did_in_chunk, last_did_in_chunk,
     964                 :      21362 :                           string(pos, end)); 
     965                 :            :     } else {
     966                 :        151 :         *from = new FlintPostlistChunkReader(first_did_in_chunk, string(pos, end));
     967                 :            :     }
     968         [ +  - ]:      21513 :     if (is_last_chunk) return Xapian::docid(-1);
     969                 :            : 
     970                 :            :     // Find first did of next tag.
     971                 :          0 :     cursor->next();
     972         [ #  # ]:          0 :     if (cursor->after_end()) {
     973                 :          0 :         throw Xapian::DatabaseCorruptError("Expected another key but found none");
     974                 :            :     }
     975                 :          0 :     const char *kpos = cursor->current_key.data();
     976                 :          0 :     const char *kend = kpos + cursor->current_key.size();
     977         [ #  # ]:          0 :     if (!check_tname_in_key(&kpos, kend, tname)) {
     978                 :          0 :         throw Xapian::DatabaseCorruptError("Expected another key with the same term name but found a different one");
     979                 :            :     }
     980                 :            : 
     981                 :            :     // Read the new first docid
     982                 :            :     Xapian::docid first_did_of_next_chunk;
     983         [ #  # ]:          0 :     if (!F_unpack_uint_preserving_sort(&kpos, kend, &first_did_of_next_chunk)) {
     984                 :          0 :         report_read_error(kpos);
     985                 :            :     }
     986                 :      21513 :     return first_did_of_next_chunk - 1;
     987                 :            : }
     988                 :            : 
     989                 :            : void
     990                 :        467 : FlintPostListTable::merge_changes(
     991                 :            :     const map<string, map<Xapian::docid, pair<char, Xapian::termcount> > > & mod_plists,
     992                 :            :     const map<Xapian::docid, Xapian::termcount> & doclens,
     993                 :            :     const map<string, pair<Xapian::termcount_diff, Xapian::termcount_diff> > & freq_deltas)
     994                 :            : {
     995                 :            :     LOGCALL_VOID(DB, "FlintPostListTable::merge_changes", mod_plists | doclens | freq_deltas);
     996                 :        467 :     map<string, map<Xapian::docid, pair<char, Xapian::termcount> > >::const_iterator i;
     997         [ +  + ]:      22105 :     for (i = mod_plists.begin(); i != mod_plists.end(); ++i) {
     998         [ -  + ]:      21638 :         if (i->second.empty()) continue;
     999                 :      21638 :         string tname = i->first;
    1000                 :            :         {
    1001                 :            :             // Rewrite the first chunk of this posting list with the updated
    1002                 :            :             // termfreq and collfreq.
    1003                 :      21638 :             map<string, pair<Xapian::termcount_diff, Xapian::termcount_diff> >::const_iterator deltas = freq_deltas.find(tname);
    1004                 :            :             Assert(deltas != freq_deltas.end());
    1005                 :            : 
    1006                 :      21638 :             string current_key = make_key(tname);
    1007                 :      21638 :             string tag;
    1008                 :      21638 :             (void)get_exact_entry(current_key, tag);
    1009                 :            : 
    1010                 :            :             // Read start of first chunk to get termfreq and collfreq.
    1011                 :      21638 :             const char *pos = tag.data();
    1012                 :      21638 :             const char *end = pos + tag.size();
    1013                 :            :             Xapian::doccount termfreq;
    1014                 :            :             Xapian::termcount collfreq;
    1015                 :            :             Xapian::docid firstdid, lastdid;
    1016                 :            :             bool islast;
    1017         [ +  + ]:      21638 :             if (pos == end) {
    1018                 :      21138 :                 termfreq = 0;
    1019                 :      21138 :                 collfreq = 0;
    1020                 :      21138 :                 firstdid = 0;
    1021                 :      21138 :                 lastdid = 0;
    1022                 :      21138 :                 islast = true;
    1023                 :            :             } else {
    1024                 :            :                 firstdid = read_start_of_first_chunk(&pos, end,
    1025                 :        500 :                                                      &termfreq, &collfreq);
    1026                 :            :                 // Handle the generic start of chunk header.
    1027                 :        500 :                 lastdid = read_start_of_chunk(&pos, end, firstdid, &islast);
    1028                 :            :             }
    1029                 :            : 
    1030                 :      21638 :             termfreq += deltas->second.first;
    1031         [ +  + ]:      21638 :             if (termfreq == 0) {
    1032                 :            :                 // All postings deleted!  So we can shortcut by zapping the
    1033                 :            :                 // posting list.
    1034         [ +  + ]:        117 :                 if (islast) {
    1035                 :            :                     // Only one entry for this posting list.
    1036                 :        107 :                     del(current_key);
    1037                 :        107 :                     continue;
    1038                 :            :                 }
    1039                 :         10 :                 AutoPtr<FlintCursor> cursor(cursor_get());
    1040                 :         10 :                 bool found = cursor->find_entry(current_key);
    1041                 :            :                 Assert(found);
    1042         [ -  + ]:         10 :                 if (!found) continue; // Reduce damage!
    1043         [ +  + ]:         38 :                 while (cursor->del()) {
    1044                 :         34 :                     const char *kpos = cursor->current_key.data();
    1045                 :         34 :                     const char *kend = kpos + cursor->current_key.size();
    1046         [ +  + ]:         34 :                     if (!check_tname_in_key_lite(&kpos, kend, tname)) break;
    1047                 :            :                 }
    1048                 :         10 :                 continue;
    1049                 :            :             }
    1050                 :      21521 :             collfreq += deltas->second.second;
    1051                 :            : 
    1052                 :            :             // Rewrite start of first chunk to update termfreq and collfreq.
    1053                 :      21521 :             string newhdr = make_start_of_first_chunk(termfreq, collfreq, firstdid);
    1054                 :      21521 :             newhdr += make_start_of_chunk(islast, firstdid, lastdid);
    1055         [ +  + ]:      21521 :             if (pos == end) {
    1056                 :      21112 :                 add(current_key, newhdr);
    1057                 :            :             } else {
    1058                 :            :                 Assert((size_t)(pos - tag.data()) <= tag.size());
    1059                 :        417 :                 tag.replace(0, pos - tag.data(), newhdr);
    1060                 :        417 :                 add(current_key, tag);
    1061 [ +  + ][ +  + ]:      21654 :             }
    1062                 :            :         }
    1063                 :      21513 :         map<Xapian::docid, pair<char, Xapian::termcount> >::const_iterator j;
    1064                 :      21513 :         j = i->second.begin();
    1065                 :            :         Assert(j != i->second.end()); // This case is caught above.
    1066                 :            : 
    1067                 :            :         Xapian::docid max_did;
    1068                 :            :         FlintPostlistChunkReader *from;
    1069                 :            :         FlintPostlistChunkWriter *to;
    1070                 :            :         max_did = get_chunk(tname, j->first, j->second.first == 'A',
    1071                 :      21513 :                             &from, &to);
    1072         [ +  + ]:     798276 :         for ( ; j != i->second.end(); ++j) {
    1073                 :     776763 :             Xapian::docid did = j->first;
    1074                 :            : 
    1075                 :            : next_chunk:
    1076                 :            :             LOGLINE(DB, "Updating tname=" << tname << ", did=" << did);
    1077 [ +  + ][ +  - ]:     784913 :             if (from) while (!from->is_at_end()) {
    1078                 :       8333 :                 Xapian::docid copy_did = from->get_docid();
    1079         [ +  + ]:       8333 :                 if (copy_did >= did) {
    1080         [ +  + ]:        183 :                     if (copy_did == did) {
    1081                 :            :                         Assert(j->second.first != 'A');
    1082                 :        180 :                         from->next();
    1083                 :            :                     }
    1084                 :        183 :                     break;
    1085                 :            :                 }
    1086                 :            :                 to->append(this, copy_did,
    1087                 :       8150 :                            from->get_wdf(), from->get_doclength());
    1088                 :       8150 :                 from->next();
    1089                 :            :             }
    1090 [ +  + ][ +  + ]:     776763 :             if ((!from || from->is_at_end()) && did > max_did) {
         [ -  + ][ -  + ]
    1091         [ #  # ]:          0 :                 delete from;
    1092                 :          0 :                 to->flush(this);
    1093         [ #  # ]:          0 :                 delete to;
    1094                 :          0 :                 max_did = get_chunk(tname, did, false, &from, &to);
    1095                 :          0 :                 goto next_chunk;
    1096                 :            :             }
    1097                 :            : 
    1098         [ +  + ]:     776763 :             if (j->second.first != 'D') {
    1099                 :     776571 :                 map<Xapian::docid, Xapian::termcount>::const_iterator k = doclens.find(did);
    1100                 :            :                 Assert(k != doclens.end());
    1101                 :     776571 :                 Xapian::termcount new_doclen = k->second;
    1102                 :     776571 :                 Xapian::termcount new_wdf = j->second.second;
    1103                 :            : 
    1104                 :     776571 :                 to->append(this, did, new_wdf, new_doclen);
    1105                 :            :             }
    1106                 :            :         }
    1107                 :            : 
    1108         [ +  + ]:      21513 :         if (from) {
    1109         [ +  + ]:        154 :             while (!from->is_at_end()) {
    1110                 :            :                 to->append(this, from->get_docid(),
    1111                 :          3 :                            from->get_wdf(), from->get_doclength());
    1112                 :          3 :                 from->next();
    1113                 :            :             }
    1114         [ +  - ]:        151 :             delete from;
    1115                 :            :         }
    1116                 :      21513 :         to->flush(this);
    1117         [ +  - ]:      21513 :         delete to;
    1118                 :            :     }
    1119                 :        459 : }

Generated by: LCOV version 1.8