LCOV - code coverage report
Current view: top level - backends/chert - chert_postlist.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core r Lines: 395 470 84.0 %
Date: 2011-08-21 Functions: 42 47 89.4 %
Branches: 188 258 72.9 %

           Branch data     Line data    Source code
       1                 :            : /* chert_postlist.cc: Postlists in a chert database
       2                 :            :  *
       3                 :            :  * Copyright 1999,2000,2001 BrightStation PLC
       4                 :            :  * Copyright 2002,2003,2004,2005,2007,2008,2009,2011 Olly Betts
       5                 :            :  * Copyright 2007,2008,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                 :            : 
      25                 :            : #include "chert_postlist.h"
      26                 :            : 
      27                 :            : #include "chert_cursor.h"
      28                 :            : #include "chert_database.h"
      29                 :            : #include "debuglog.h"
      30                 :            : #include "noreturn.h"
      31                 :            : #include "pack.h"
      32                 :            : #include "str.h"
      33                 :            : 
      34                 :            : Xapian::doccount
      35                 :     194371 : ChertPostListTable::get_termfreq(const string & term) const
      36                 :            : {
      37                 :     194371 :     string key = make_key(term);
      38                 :     194371 :     string tag;
      39         [ +  + ]:     194371 :     if (!get_exact_entry(key, tag)) return 0;
      40                 :            : 
      41                 :            :     Xapian::doccount termfreq;
      42                 :     170017 :     const char * p = tag.data();
      43                 :     170017 :     ChertPostList::read_number_of_entries(&p, p + tag.size(), &termfreq, NULL);
      44                 :     194372 :     return termfreq;
      45                 :            : }
      46                 :            : 
      47                 :            : Xapian::termcount
      48                 :     237067 : ChertPostListTable::get_collection_freq(const string & term) const
      49                 :            : {
      50                 :     237067 :     string key = make_key(term);
      51                 :     237067 :     string tag;
      52         [ +  + ]:     237067 :     if (!get_exact_entry(key, tag)) return 0;
      53                 :            : 
      54                 :            :     Xapian::termcount collfreq;
      55                 :     189815 :     const char * p = tag.data();
      56                 :     189815 :     ChertPostList::read_number_of_entries(&p, p + tag.size(), NULL, &collfreq);
      57                 :     237067 :     return collfreq;
      58                 :            : }
      59                 :            : 
      60                 :            : Xapian::termcount
      61                 :   19120662 : ChertPostListTable::get_doclength(Xapian::docid did,
      62                 :            :                                   Xapian::Internal::RefCntPtr<const ChertDatabase> db) const {
      63         [ +  + ]:   19120662 :     if (!doclen_pl.get()) {
      64                 :            :         // Don't keep a reference back to the database, since this
      65                 :            :         // would make a reference loop.
      66                 :        761 :         doclen_pl.reset(new ChertPostList(db, string(), false));
      67                 :            :     }
      68         [ +  + ]:   19120662 :     if (!doclen_pl->jump_to(did))
      69                 :      24079 :         throw Xapian::DocNotFoundError("Document " + str(did) + " not found");
      70                 :   19096583 :     return doclen_pl->get_wdf();
      71                 :            : }
      72                 :            : 
      73                 :            : bool
      74                 :          0 : ChertPostListTable::document_exists(Xapian::docid did,
      75                 :            :                                     Xapian::Internal::RefCntPtr<const ChertDatabase> db) const
      76                 :            : {
      77         [ #  # ]:          0 :     if (!doclen_pl.get()) {
      78                 :            :         // Don't keep a reference back to the database, since this
      79                 :            :         // would make a reference loop.
      80                 :          0 :         doclen_pl.reset(new ChertPostList(db, string(), false));
      81                 :            :     }
      82                 :          0 :     return (doclen_pl->jump_to(did));
      83                 :            : }
      84                 :            : 
      85                 :            : // How big should chunks in the posting list be?  (They
      86                 :            : // will grow slightly bigger than this, but not more than a
      87                 :            : // few bytes extra) - FIXME: tune this value to try to
      88                 :            : // maximise how well blocks are used.  Or performance.
      89                 :            : // Or indexing speed.  Or something...
      90                 :            : const unsigned int CHUNKSIZE = 2000;
      91                 :            : 
      92                 :            : /** PostlistChunkWriter is a wrapper which acts roughly as an
      93                 :            :  *  output iterator on a postlist chunk, taking care of the
      94                 :            :  *  messy details.  It's intended to be used with deletion and
      95                 :            :  *  replacing of entries, not for adding to the end, when it's
      96                 :            :  *  not really needed.
      97                 :            :  */
      98                 :      21869 : class Chert::PostlistChunkWriter {
      99                 :            :     public:
     100                 :            :         PostlistChunkWriter(const string &orig_key_,
     101                 :            :                             bool is_first_chunk_,
     102                 :            :                             const string &tname_,
     103                 :            :                             bool is_last_chunk_);
     104                 :            : 
     105                 :            :         /// Append an entry to this chunk.
     106                 :            :         void append(ChertTable * table, Xapian::docid did,
     107                 :            :                     Xapian::termcount wdf);
     108                 :            : 
     109                 :            :         /// Append a block of raw entries to this chunk.
     110                 :      21715 :         void raw_append(Xapian::docid first_did_, Xapian::docid current_did_,
     111                 :            :                         const string & s) {
     112                 :            :             Assert(!started);
     113                 :      21715 :             first_did = first_did_;
     114                 :      21715 :             current_did = current_did_;
     115         [ +  + ]:      21715 :             if (!s.empty()) {
     116                 :        343 :                 chunk.append(s);
     117                 :        343 :                 started = true;
     118                 :            :             }
     119                 :      21715 :         }
     120                 :            : 
     121                 :            :         /** Flush the chunk to the buffered table.  Note: this may write it
     122                 :            :          *  with a different key to the original one, if for example the first
     123                 :            :          *  entry has changed.
     124                 :            :          */
     125                 :            :         void flush(ChertTable *table);
     126                 :            : 
     127                 :            :     private:
     128                 :            :         string orig_key;
     129                 :            :         string tname;
     130                 :            :         bool is_first_chunk;
     131                 :            :         bool is_last_chunk;
     132                 :            :         bool started;
     133                 :            : 
     134                 :            :         Xapian::docid first_did;
     135                 :            :         Xapian::docid current_did;
     136                 :            : 
     137                 :            :         string chunk;
     138                 :            : };
     139                 :            : 
     140                 :            : using Chert::PostlistChunkWriter;
     141                 :            : 
     142                 :            : // Static functions
     143                 :            : 
     144                 :            : /// Report an error when reading the posting list.
     145                 :            : XAPIAN_NORETURN(static void report_read_error(const char * position));
     146                 :          0 : static void report_read_error(const char * position)
     147                 :            : {
     148         [ #  # ]:          0 :     if (position == 0) {
     149                 :            :         // data ran out
     150                 :            :         LOGLINE(DB, "ChertPostList data ran out");
     151                 :          0 :         throw Xapian::DatabaseCorruptError("Data ran out unexpectedly when reading posting list.");
     152                 :            :     }
     153                 :            :     // overflow
     154                 :            :     LOGLINE(DB, "ChertPostList value too large");
     155                 :          0 :     throw Xapian::RangeError("Value in posting list too large.");
     156                 :            : }
     157                 :            : 
     158                 :      21776 : static inline bool get_tname_from_key(const char **src, const char *end,
     159                 :            :                                string &tname)
     160                 :            : {
     161                 :      21776 :     return unpack_string_preserving_sort(src, end, tname);
     162                 :            : }
     163                 :            : 
     164                 :            : static inline bool
     165                 :     105699 : check_tname_in_key_lite(const char **keypos, const char *keyend, const string &tname)
     166                 :            : {
     167                 :     105699 :     string tname_in_key;
     168                 :            : 
     169   [ +  +  +  + ]:     105699 :     if (keyend - *keypos >= 2 && (*keypos)[0] == '\0' && (*keypos)[1] == '\xe0') {
                 [ +  - ]
     170                 :      83923 :         *keypos += 2;
     171                 :            :     } else {
     172                 :            :         // Read the termname.
     173         [ -  + ]:      21776 :         if (!get_tname_from_key(keypos, keyend, tname_in_key))
     174                 :          0 :             report_read_error(*keypos);
     175                 :            :     }
     176                 :            : 
     177                 :            :     // This should only fail if the postlist doesn't exist at all.
     178                 :     105699 :     return tname_in_key == tname;
     179                 :            : }
     180                 :            : 
     181                 :            : static inline bool
     182                 :      21959 : check_tname_in_key(const char **keypos, const char *keyend, const string &tname)
     183                 :            : {
     184         [ -  + ]:      21959 :     if (*keypos == keyend) return false;
     185                 :            : 
     186                 :      21959 :     return check_tname_in_key_lite(keypos, keyend, tname);
     187                 :            : }
     188                 :            : 
     189                 :            : /// Read the start of the first chunk in the posting list.
     190                 :            : static Xapian::docid
     191                 :     248591 : read_start_of_first_chunk(const char ** posptr,
     192                 :            :                           const char * end,
     193                 :            :                           Xapian::doccount * number_of_entries_ptr,
     194                 :            :                           Xapian::termcount * collection_freq_ptr)
     195                 :            : {
     196                 :            :     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);
     197                 :            : 
     198                 :            :     ChertPostList::read_number_of_entries(posptr, end,
     199                 :     248591 :                            number_of_entries_ptr, collection_freq_ptr);
     200                 :            :     if (number_of_entries_ptr)
     201                 :            :         LOGVALUE(DB, *number_of_entries_ptr);
     202                 :            :     if (collection_freq_ptr)
     203                 :            :         LOGVALUE(DB, *collection_freq_ptr);
     204                 :            : 
     205                 :            :     Xapian::docid did;
     206                 :            :     // Read the docid of the first entry in the posting list.
     207         [ -  + ]:     248591 :     if (!unpack_uint(posptr, end, &did))
     208                 :          0 :         report_read_error(*posptr);
     209                 :     248591 :     ++did;
     210                 :            :     LOGVALUE(DB, did);
     211                 :     248591 :     RETURN(did);
     212                 :            : }
     213                 :            : 
     214                 :            : static inline void
     215                 :   45849580 : read_did_increase(const char ** posptr, const char * end,
     216                 :            :                   Xapian::docid * did_ptr)
     217                 :            : {
     218                 :            :     Xapian::docid did_increase;
     219         [ -  + ]:   45849580 :     if (!unpack_uint(posptr, end, &did_increase)) report_read_error(*posptr);
     220                 :   45849580 :     *did_ptr += did_increase + 1;
     221                 :   45849580 : }
     222                 :            : 
     223                 :            : /// Read the wdf for an entry.
     224                 :            : static inline void
     225                 :   46054691 : read_wdf(const char ** posptr, const char * end, Xapian::termcount * wdf_ptr)
     226                 :            : {
     227         [ -  + ]:   46054691 :     if (!unpack_uint(posptr, end, wdf_ptr)) report_read_error(*posptr);
     228                 :   46054691 : }
     229                 :            : 
     230                 :            : /// Read the start of a chunk.
     231                 :            : static Xapian::docid
     232                 :     227233 : read_start_of_chunk(const char ** posptr,
     233                 :            :                     const char * end,
     234                 :            :                     Xapian::docid first_did_in_chunk,
     235                 :            :                     bool * is_last_chunk_ptr)
     236                 :            : {
     237                 :            :     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));
     238                 :            :     Assert(is_last_chunk_ptr);
     239                 :            : 
     240                 :            :     // Read whether this is the last chunk
     241         [ -  + ]:     227233 :     if (!unpack_bool(posptr, end, is_last_chunk_ptr))
     242                 :          0 :         report_read_error(*posptr);
     243                 :            :     LOGVALUE(DB, *is_last_chunk_ptr);
     244                 :            : 
     245                 :            :     // Read what the final document ID in this chunk is.
     246                 :            :     Xapian::docid increase_to_last;
     247         [ -  + ]:     227233 :     if (!unpack_uint(posptr, end, &increase_to_last))
     248                 :          0 :         report_read_error(*posptr);
     249                 :     227233 :     Xapian::docid last_did_in_chunk = first_did_in_chunk + increase_to_last;
     250                 :            :     LOGVALUE(DB, last_did_in_chunk);
     251                 :     227233 :     RETURN(last_did_in_chunk);
     252                 :            : }
     253                 :            : 
     254                 :            : /** PostlistChunkReader is essentially an iterator wrapper
     255                 :            :  *  around a postlist chunk.  It simply iterates through the
     256                 :            :  *  entries in a postlist.
     257                 :            :  */
     258                 :        154 : class Chert::PostlistChunkReader {
     259                 :            :     string data;
     260                 :            : 
     261                 :            :     const char *pos;
     262                 :            :     const char *end;
     263                 :            : 
     264                 :            :     bool at_end;
     265                 :            : 
     266                 :            :     Xapian::docid did;
     267                 :            :     Xapian::termcount wdf;
     268                 :            : 
     269                 :            :   public:
     270                 :            :     /** Initialise the postlist chunk reader.
     271                 :            :      *
     272                 :            :      *  @param first_did  First document id in this chunk.
     273                 :            :      *  @param data       The tag string with the header removed.
     274                 :            :      */
     275                 :        154 :     PostlistChunkReader(Xapian::docid first_did, const string & data_)
     276                 :        154 :         : data(data_), pos(data.data()), end(pos + data.length()), at_end(data.empty()), did(first_did)
     277                 :            :     {
     278         [ +  - ]:        154 :         if (!at_end) read_wdf(&pos, end, &wdf);
     279                 :        154 :     }
     280                 :            : 
     281                 :      20220 :     Xapian::docid get_docid() const {
     282                 :      20220 :         return did;
     283                 :            :     }
     284                 :      10228 :     Xapian::termcount get_wdf() const {
     285                 :      10228 :         return wdf;
     286                 :            :     }
     287                 :            : 
     288                 :      30384 :     bool is_at_end() const {
     289                 :      30384 :         return at_end;
     290                 :            :     }
     291                 :            : 
     292                 :            :     /** Advance to the next entry.  Set at_end if we run off the end.
     293                 :            :      */
     294                 :            :     void next();
     295                 :            : };
     296                 :            : 
     297                 :            : using Chert::PostlistChunkReader;
     298                 :            : 
     299                 :            : void
     300                 :      20211 : PostlistChunkReader::next()
     301                 :            : {
     302         [ +  + ]:      20211 :     if (pos == end) {
     303                 :        154 :         at_end = true;
     304                 :            :     } else {
     305                 :      20057 :         read_did_increase(&pos, end, &did);
     306                 :      20057 :         read_wdf(&pos, end, &wdf);
     307                 :            :     }
     308                 :      20211 : }
     309                 :            : 
     310                 :      21869 : PostlistChunkWriter::PostlistChunkWriter(const string &orig_key_,
     311                 :            :                                          bool is_first_chunk_,
     312                 :            :                                          const string &tname_,
     313                 :            :                                          bool is_last_chunk_)
     314                 :            :         : orig_key(orig_key_),
     315                 :            :           tname(tname_), is_first_chunk(is_first_chunk_),
     316                 :            :           is_last_chunk(is_last_chunk_),
     317                 :      21869 :           started(false)
     318                 :            : {
     319                 :            :     LOGCALL_VOID(DB, "PostlistChunkWriter::PostlistChunkWriter", orig_key_ | is_first_chunk_ | tname_ | is_last_chunk_);
     320                 :      21869 : }
     321                 :            : 
     322                 :            : void
     323                 :     854897 : PostlistChunkWriter::append(ChertTable * table, Xapian::docid did,
     324                 :            :                             Xapian::termcount wdf)
     325                 :            : {
     326         [ +  + ]:     854897 :     if (!started) {
     327                 :      21501 :         started = true;
     328                 :      21501 :         first_did = did;
     329                 :            :     } else {
     330                 :            :         Assert(did > current_did);
     331                 :            :         // Start a new chunk if this one has grown to the threshold.
     332         [ +  + ]:     833396 :         if (chunk.size() >= CHUNKSIZE) {
     333                 :         69 :             bool save_is_last_chunk = is_last_chunk;
     334                 :         69 :             is_last_chunk = false;
     335                 :         69 :             flush(table);
     336                 :         69 :             is_last_chunk = save_is_last_chunk;
     337                 :         69 :             is_first_chunk = false;
     338                 :         69 :             first_did = did;
     339                 :         69 :             chunk.resize(0);
     340                 :         69 :             orig_key = ChertPostListTable::make_key(tname, first_did);
     341                 :            :         } else {
     342                 :     833327 :             pack_uint(chunk, did - current_did - 1);
     343                 :            :         }
     344                 :            :     }
     345                 :     854897 :     current_did = did;
     346                 :     854897 :     pack_uint(chunk, wdf);
     347                 :     854897 : }
     348                 :            : 
     349                 :            : /** Make the data to go at the start of the very first chunk.
     350                 :            :  */
     351                 :            : static inline string
     352                 :      43539 : make_start_of_first_chunk(Xapian::doccount entries,
     353                 :            :                           Xapian::termcount collectionfreq,
     354                 :            :                           Xapian::docid new_did)
     355                 :            : {
     356                 :      43539 :     string chunk;
     357                 :      43539 :     pack_uint(chunk, entries);
     358                 :      43539 :     pack_uint(chunk, collectionfreq);
     359                 :      43539 :     pack_uint(chunk, new_did - 1);
     360                 :          0 :     return chunk;
     361                 :            : }
     362                 :            : 
     363                 :            : /** Make the data to go at the start of a standard chunk.
     364                 :            :  */
     365                 :            : static inline string
     366                 :      43617 : make_start_of_chunk(bool new_is_last_chunk,
     367                 :            :                     Xapian::docid new_first_did,
     368                 :            :                     Xapian::docid new_final_did)
     369                 :            : {
     370                 :            :     Assert(new_final_did >= new_first_did);
     371                 :      43617 :     string chunk;
     372                 :      43617 :     pack_bool(chunk, new_is_last_chunk);
     373                 :      43617 :     pack_uint(chunk, new_final_did - new_first_did);
     374                 :          0 :     return chunk;
     375                 :            : }
     376                 :            : 
     377                 :            : static void
     378                 :          0 : write_start_of_chunk(string & chunk,
     379                 :            :                      unsigned int start_of_chunk_header,
     380                 :            :                      unsigned int end_of_chunk_header,
     381                 :            :                      bool is_last_chunk,
     382                 :            :                      Xapian::docid first_did_in_chunk,
     383                 :            :                      Xapian::docid last_did_in_chunk)
     384                 :            : {
     385                 :            :     Assert((size_t)(end_of_chunk_header - start_of_chunk_header) <= chunk.size());
     386                 :            : 
     387                 :            :     chunk.replace(start_of_chunk_header,
     388                 :            :                   end_of_chunk_header - start_of_chunk_header,
     389                 :            :                   make_start_of_chunk(is_last_chunk, first_did_in_chunk,
     390                 :          0 :                                       last_did_in_chunk));
     391                 :          0 : }
     392                 :            : 
     393                 :            : void
     394                 :      21938 : PostlistChunkWriter::flush(ChertTable *table)
     395                 :            : {
     396                 :            :     LOGCALL_VOID(DB, "PostlistChunkWriter::flush", table);
     397                 :            : 
     398                 :            :     /* This is one of the more messy parts involved with updating posting
     399                 :            :      * list chunks.
     400                 :            :      *
     401                 :            :      * Depending on circumstances, we may have to delete an entire chunk
     402                 :            :      * or file it under a different key, as well as possibly modifying both
     403                 :            :      * the previous and next chunk of the postlist.
     404                 :            :      */
     405                 :            : 
     406         [ +  + ]:      21938 :     if (!started) {
     407                 :            :         /* This chunk is now empty so disappears entirely.
     408                 :            :          *
     409                 :            :          * If this was the last chunk, then the previous chunk
     410                 :            :          * must have its "is_last_chunk" flag updated.
     411                 :            :          *
     412                 :            :          * If this was the first chunk, then the next chunk must
     413                 :            :          * be transformed into the first chunk.  Messy!
     414                 :            :          */
     415                 :            :         LOGLINE(DB, "PostlistChunkWriter::flush(): deleting chunk");
     416                 :            :         Assert(!orig_key.empty());
     417         [ +  - ]:         25 :         if (is_first_chunk) {
     418                 :            :             LOGLINE(DB, "PostlistChunkWriter::flush(): deleting first chunk");
     419         [ +  + ]:         25 :             if (is_last_chunk) {
     420                 :            :                 /* This is the first and the last chunk, ie the only
     421                 :            :                  * chunk, so just delete the tag.
     422                 :            :                  */
     423                 :         19 :                 table->del(orig_key);
     424                 :         19 :                 return;
     425                 :            :             }
     426                 :            : 
     427                 :            :             /* This is the messiest case.  The first chunk is to
     428                 :            :              * be removed, and there is at least one chunk after
     429                 :            :              * it.  Need to rewrite the next chunk as the first
     430                 :            :              * chunk.
     431                 :            :              */
     432                 :          6 :             AutoPtr<ChertCursor> cursor(table->cursor_get());
     433                 :            : 
     434         [ -  + ]:          6 :             if (!cursor->find_entry(orig_key)) {
     435                 :          0 :                 throw Xapian::DatabaseCorruptError("The key we're working on has disappeared");
     436                 :            :             }
     437                 :            : 
     438                 :            :             // FIXME: Currently the doclen list has a special first chunk too,
     439                 :            :             // which reduces special casing here.  The downside is a slightly
     440                 :            :             // larger than necessary first chunk and needless fiddling if the
     441                 :            :             // first chunk is deleted.  But really we should look at
     442                 :            :             // redesigning the whole postlist format with an eye to making it
     443                 :            :             // easier to update!
     444                 :            : 
     445                 :            :             // Extract existing counts from the first chunk so we can reinsert
     446                 :            :             // them into the block we're renaming.
     447                 :            :             Xapian::doccount num_ent;
     448                 :            :             Xapian::termcount coll_freq;
     449                 :            :             {
     450                 :          6 :                 cursor->read_tag();
     451                 :          6 :                 const char *tagpos = cursor->current_tag.data();
     452                 :          6 :                 const char *tagend = tagpos + cursor->current_tag.size();
     453                 :            : 
     454                 :            :                 (void)read_start_of_first_chunk(&tagpos, tagend,
     455                 :          6 :                                                 &num_ent, &coll_freq);
     456                 :            :             }
     457                 :            : 
     458                 :            :             // Seek to the next chunk.
     459                 :          6 :             cursor->next();
     460         [ -  + ]:          6 :             if (cursor->after_end()) {
     461                 :          0 :                 throw Xapian::DatabaseCorruptError("Expected another key but found none");
     462                 :            :             }
     463                 :          6 :             const char *kpos = cursor->current_key.data();
     464                 :          6 :             const char *kend = kpos + cursor->current_key.size();
     465         [ -  + ]:          6 :             if (!check_tname_in_key(&kpos, kend, tname)) {
     466                 :          0 :                 throw Xapian::DatabaseCorruptError("Expected another key with the same term name but found a different one");
     467                 :            :             }
     468                 :            : 
     469                 :            :             // Read the new first docid
     470                 :            :             Xapian::docid new_first_did;
     471         [ -  + ]:          6 :             if (!unpack_uint_preserving_sort(&kpos, kend, &new_first_did)) {
     472                 :          0 :                 report_read_error(kpos);
     473                 :            :             }
     474                 :            : 
     475                 :          6 :             cursor->read_tag();
     476                 :          6 :             const char *tagpos = cursor->current_tag.data();
     477                 :          6 :             const char *tagend = tagpos + cursor->current_tag.size();
     478                 :            : 
     479                 :            :             // Read the chunk header
     480                 :            :             bool new_is_last_chunk;
     481                 :            :             Xapian::docid new_last_did_in_chunk =
     482                 :            :                 read_start_of_chunk(&tagpos, tagend, new_first_did,
     483                 :          6 :                                     &new_is_last_chunk);
     484                 :            : 
     485                 :          6 :             string chunk_data(tagpos, tagend);
     486                 :            : 
     487                 :            :             // First remove the renamed tag
     488                 :          6 :             table->del(cursor->current_key);
     489                 :            : 
     490                 :            :             // And now write it as the first chunk
     491                 :          6 :             string tag;
     492                 :          6 :             tag = make_start_of_first_chunk(num_ent, coll_freq, new_first_did);
     493                 :            :             tag += make_start_of_chunk(new_is_last_chunk,
     494                 :            :                                               new_first_did,
     495                 :          6 :                                               new_last_did_in_chunk);
     496                 :          6 :             tag += chunk_data;
     497                 :          6 :             table->add(orig_key, tag);
     498                 :          6 :             return;
     499                 :            :         }
     500                 :            : 
     501                 :            :         LOGLINE(DB, "PostlistChunkWriter::flush(): deleting secondary chunk");
     502                 :            :         /* This isn't the first chunk.  Check whether we're the last chunk. */
     503                 :            : 
     504                 :            :         // Delete this chunk
     505                 :          0 :         table->del(orig_key);
     506                 :            : 
     507         [ #  # ]:          0 :         if (is_last_chunk) {
     508                 :            :             LOGLINE(DB, "PostlistChunkWriter::flush(): deleting secondary last chunk");
     509                 :            :             // Update the previous chunk's is_last_chunk flag.
     510                 :          0 :             AutoPtr<ChertCursor> cursor(table->cursor_get());
     511                 :            : 
     512                 :            :             /* Should not find the key we just deleted, but should
     513                 :            :              * find the previous chunk. */
     514         [ #  # ]:          0 :             if (cursor->find_entry(orig_key)) {
     515                 :          0 :                 throw Xapian::DatabaseCorruptError("Chert key not deleted as we expected");
     516                 :            :             }
     517                 :            :             // Make sure this is a chunk with the right term attached.
     518                 :          0 :             const char * keypos = cursor->current_key.data();
     519                 :          0 :             const char * keyend = keypos + cursor->current_key.size();
     520         [ #  # ]:          0 :             if (!check_tname_in_key(&keypos, keyend, tname)) {
     521                 :          0 :                 throw Xapian::DatabaseCorruptError("Couldn't find chunk before delete chunk");
     522                 :            :             }
     523                 :            : 
     524                 :          0 :             bool is_prev_first_chunk = (keypos == keyend);
     525                 :            : 
     526                 :            :             // Now update the last_chunk
     527                 :          0 :             cursor->read_tag();
     528                 :          0 :             string tag = cursor->current_tag;
     529                 :            : 
     530                 :          0 :             const char *tagpos = tag.data();
     531                 :          0 :             const char *tagend = tagpos + tag.size();
     532                 :            : 
     533                 :            :             // Skip first chunk header
     534                 :            :             Xapian::docid first_did_in_chunk;
     535         [ #  # ]:          0 :             if (is_prev_first_chunk) {
     536                 :            :                 first_did_in_chunk = read_start_of_first_chunk(&tagpos, tagend,
     537                 :          0 :                                                                0, 0);
     538                 :            :             } else {
     539         [ #  # ]:          0 :                 if (!unpack_uint_preserving_sort(&keypos, keyend, &first_did_in_chunk))
     540                 :          0 :                     report_read_error(keypos);
     541                 :            :             }
     542                 :            :             bool wrong_is_last_chunk;
     543                 :          0 :             string::size_type start_of_chunk_header = tagpos - tag.data();
     544                 :            :             Xapian::docid last_did_in_chunk =
     545                 :            :                 read_start_of_chunk(&tagpos, tagend, first_did_in_chunk,
     546                 :          0 :                                     &wrong_is_last_chunk);
     547                 :          0 :             string::size_type end_of_chunk_header = tagpos - tag.data();
     548                 :            : 
     549                 :            :             // write new is_last flag
     550                 :            :             write_start_of_chunk(tag,
     551                 :            :                                  start_of_chunk_header,
     552                 :            :                                  end_of_chunk_header,
     553                 :            :                                  true, // is_last_chunk
     554                 :            :                                  first_did_in_chunk,
     555                 :          0 :                                  last_did_in_chunk);
     556                 :          0 :             table->add(cursor->current_key, tag);
     557                 :            :         }
     558                 :            :     } else {
     559                 :            :         LOGLINE(DB, "PostlistChunkWriter::flush(): updating chunk which still has items in it");
     560                 :            :         /* The chunk still has some items in it.  Two major subcases:
     561                 :            :          * a) This is the first chunk.
     562                 :            :          * b) This isn't the first chunk.
     563                 :            :          *
     564                 :            :          * The subcases just affect the chunk header.
     565                 :            :          */
     566                 :      21913 :         string tag;
     567                 :            : 
     568                 :            :         /* First write the header, which depends on whether this is the
     569                 :            :          * first chunk.
     570                 :            :          */
     571         [ +  + ]:      21913 :         if (is_first_chunk) {
     572                 :            :             /* The first chunk.  This is the relatively easy case,
     573                 :            :              * and we just have to write this one back to disk.
     574                 :            :              */
     575                 :            :             LOGLINE(DB, "PostlistChunkWriter::flush(): rewriting the first chunk, which still has items in it");
     576                 :      21835 :             string key = ChertPostListTable::make_key(tname);
     577                 :      21835 :             bool ok = table->get_exact_entry(key, tag);
     578                 :            :             (void)ok;
     579                 :            :             Assert(ok);
     580                 :            :             Assert(!tag.empty());
     581                 :            : 
     582                 :            :             Xapian::doccount num_ent;
     583                 :            :             Xapian::termcount coll_freq;
     584                 :            :             {
     585                 :      21835 :                 const char * tagpos = tag.data();
     586                 :      21835 :                 const char * tagend = tagpos + tag.size();
     587                 :            :                 (void)read_start_of_first_chunk(&tagpos, tagend,
     588                 :      21835 :                                                 &num_ent, &coll_freq);
     589                 :            :             }
     590                 :            : 
     591                 :      21835 :             tag = make_start_of_first_chunk(num_ent, coll_freq, first_did);
     592                 :            : 
     593                 :      21835 :             tag += make_start_of_chunk(is_last_chunk, first_did, current_did);
     594                 :      21835 :             tag += chunk;
     595                 :      21835 :             table->add(key, tag);
     596                 :      21835 :             return;
     597                 :            :         }
     598                 :            : 
     599                 :            :         LOGLINE(DB, "PostlistChunkWriter::flush(): updating secondary chunk which still has items in it");
     600                 :            :         /* Not the first chunk.
     601                 :            :          *
     602                 :            :          * This has the easy sub-sub-case:
     603                 :            :          *   The first entry in the chunk hasn't changed
     604                 :            :          * ...and the hard sub-sub-case:
     605                 :            :          *   The first entry in the chunk has changed.  This is
     606                 :            :          *   harder because the key for the chunk changes, so
     607                 :            :          *   we've got to do a switch.
     608                 :            :          */
     609                 :            : 
     610                 :            :         // First find out the initial docid
     611                 :         78 :         const char *keypos = orig_key.data();
     612                 :         78 :         const char *keyend = keypos + orig_key.size();
     613         [ -  + ]:         78 :         if (!check_tname_in_key(&keypos, keyend, tname)) {
     614                 :          0 :             throw Xapian::DatabaseCorruptError("Have invalid key writing to postlist");
     615                 :            :         }
     616                 :            :         Xapian::docid initial_did;
     617         [ -  + ]:         78 :         if (!unpack_uint_preserving_sort(&keypos, keyend, &initial_did)) {
     618                 :          0 :             report_read_error(keypos);
     619                 :            :         }
     620                 :         78 :         string new_key;
     621         [ -  + ]:         78 :         if (initial_did != first_did) {
     622                 :            :             /* The fiddlier case:
     623                 :            :              * Create a new tag with the correct key, and replace
     624                 :            :              * the old one.
     625                 :            :              */
     626                 :          0 :             new_key = ChertPostListTable::make_key(tname, first_did);
     627                 :          0 :             table->del(orig_key);
     628                 :            :         } else {
     629                 :         78 :             new_key = orig_key;
     630                 :            :         }
     631                 :            : 
     632                 :            :         // ...and write the start of this chunk.
     633                 :         78 :         tag = make_start_of_chunk(is_last_chunk, first_did, current_did);
     634                 :            : 
     635                 :         78 :         tag += chunk;
     636                 :      21938 :         table->add(new_key, tag);
     637                 :            :     }
     638                 :            : }
     639                 :            : 
     640                 :            : /** Read the number of entries in the posting list.
     641                 :            :  *  This must only be called when *posptr is pointing to the start of
     642                 :            :  *  the first chunk of the posting list.
     643                 :            :  */
     644                 :     609421 : void ChertPostList::read_number_of_entries(const char ** posptr,
     645                 :            :                                    const char * end,
     646                 :            :                                    Xapian::doccount * number_of_entries_ptr,
     647                 :            :                                    Xapian::termcount * collection_freq_ptr)
     648                 :            : {
     649         [ -  + ]:     609421 :     if (!unpack_uint(posptr, end, number_of_entries_ptr))
     650                 :          0 :         report_read_error(*posptr);
     651         [ -  + ]:     609421 :     if (!unpack_uint(posptr, end, collection_freq_ptr))
     652                 :          0 :         report_read_error(*posptr);
     653                 :     609421 : }
     654                 :            : 
     655                 :            : /** The format of a postlist is:
     656                 :            :  *
     657                 :            :  *  Split into chunks.  Key for first chunk is the termname (encoded as
     658                 :            :  *  length : name).  Key for subsequent chunks is the same, followed by the
     659                 :            :  *  document ID of the first document in the chunk (encoded as length of
     660                 :            :  *  representation in first byte, and then docid).
     661                 :            :  *
     662                 :            :  *  A chunk (except for the first chunk) contains:
     663                 :            :  *
     664                 :            :  *  1)  bool - true if this is the last chunk.
     665                 :            :  *  2)  difference between final docid in chunk and first docid.
     666                 :            :  *  3)  wdf for the first item.
     667                 :            :  *  4)  increment in docid to next item, followed by wdf for the item.
     668                 :            :  *  5)  (4) repeatedly.
     669                 :            :  *
     670                 :            :  *  The first chunk begins with the number of entries, the collection
     671                 :            :  *  frequency, then the docid of the first document, then has the header of a
     672                 :            :  *  standard chunk.
     673                 :            :  */
     674                 :     145302 : ChertPostList::ChertPostList(Xapian::Internal::RefCntPtr<const ChertDatabase> this_db_,
     675                 :            :                              const string & term_,
     676                 :            :                              bool keep_reference)
     677                 :            :         : LeafPostList(term_),
     678                 :            :           this_db(keep_reference ? this_db_ : NULL),
     679                 :            :           have_started(false),
     680                 :            :           cursor(this_db_->postlist_table.cursor_get()),
     681 [ +  + ][ +  - ]:     145302 :           is_at_end(false)
     682                 :            : {
     683                 :            :     LOGCALL_VOID(DB, "ChertPostList::ChertPostList", this_db_.get() | term_ | keep_reference);
     684                 :     145297 :     string key = ChertPostListTable::make_key(term);
     685                 :     145297 :     int found = cursor->find_entry(key);
     686   [ +  +  +  + ]:     145297 :     if (!found) {
     687                 :            :         LOGLINE(DB, "postlist for term not found");
     688                 :      24065 :         number_of_entries = 0;
     689                 :      24065 :         is_at_end = true;
     690                 :      24065 :         pos = 0;
     691                 :      24065 :         end = 0;
     692                 :      24065 :         first_did_in_chunk = 0;
     693                 :      24065 :         last_did_in_chunk = 0;
     694                 :            :         return;
     695                 :            :     }
     696                 :     121232 :     cursor->read_tag();
     697                 :     121232 :     pos = cursor->current_tag.data();
     698                 :     121232 :     end = pos + cursor->current_tag.size();
     699                 :            : 
     700                 :     121232 :     did = read_start_of_first_chunk(&pos, end, &number_of_entries, NULL);
     701                 :     121232 :     first_did_in_chunk = did;
     702                 :            :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk,
     703                 :     121232 :                                             &is_last_chunk);
     704                 :     121232 :     read_wdf(&pos, end, &wdf);
     705                 :     145297 :     LOGLINE(DB, "Initial docid " << did);
     706                 :     145312 : }
     707                 :            : 
     708                 :     145297 : ChertPostList::~ChertPostList()
     709                 :            : {
     710                 :            :     LOGCALL_VOID(DB, "ChertPostList::~ChertPostList", NO_ARGS);
     711 [ +  - ][ #  # ]:     145297 : }
                 [ -  + ]
     712                 :            : 
     713                 :            : Xapian::termcount
     714                 :   18870203 : ChertPostList::get_doclength() const
     715                 :            : {
     716                 :            :     LOGCALL(DB, Xapian::termcount, "ChertPostList::get_doclength", NO_ARGS);
     717                 :            :     Assert(have_started);
     718                 :            :     Assert(this_db.get());
     719                 :   18870203 :     RETURN(this_db->get_doclength(did));
     720                 :            : }
     721                 :            : 
     722                 :            : bool
     723                 :   18879952 : ChertPostList::next_in_chunk()
     724                 :            : {
     725                 :            :     LOGCALL(DB, bool, "ChertPostList::next_in_chunk", NO_ARGS);
     726         [ +  + ]:   18879952 :     if (pos == end) RETURN(false);
     727                 :            : 
     728                 :   18761732 :     read_did_increase(&pos, end, &did);
     729                 :   18761732 :     read_wdf(&pos, end, &wdf);
     730                 :            : 
     731                 :            :     // Either not at last doc in chunk, or pos == end, but not both.
     732                 :            :     Assert(did <= last_did_in_chunk);
     733                 :            :     Assert(did < last_did_in_chunk || pos == end);
     734                 :            :     Assert(pos != end || did == last_did_in_chunk);
     735                 :            : 
     736                 :   18879952 :     RETURN(true);
     737                 :            : }
     738                 :            : 
     739                 :            : void
     740                 :     118532 : ChertPostList::next_chunk()
     741                 :            : {
     742                 :            :     LOGCALL_VOID(DB, "ChertPostList::next_chunk", NO_ARGS);
     743         [ +  + ]:     118532 :     if (is_last_chunk) {
     744                 :     118335 :         is_at_end = true;
     745                 :     118335 :         return;
     746                 :            :     }
     747                 :            : 
     748                 :        197 :     cursor->next();
     749         [ -  + ]:        197 :     if (cursor->after_end()) {
     750                 :          0 :         is_at_end = true;
     751                 :            :         throw Xapian::DatabaseCorruptError("Unexpected end of posting list for `" +
     752                 :          0 :                                      term + "'");
     753                 :            :     }
     754                 :        197 :     const char * keypos = cursor->current_key.data();
     755                 :        197 :     const char * keyend = keypos + cursor->current_key.size();
     756                 :            :     // Check we're still in same postlist
     757         [ -  + ]:        197 :     if (!check_tname_in_key_lite(&keypos, keyend, term)) {
     758                 :          0 :         is_at_end = true;
     759                 :            :         throw Xapian::DatabaseCorruptError("Unexpected end of posting list for `" +
     760                 :          0 :                                      term + "'");
     761                 :            :     }
     762                 :            : 
     763                 :            :     Xapian::docid newdid;
     764         [ -  + ]:        197 :     if (!unpack_uint_preserving_sort(&keypos, keyend, &newdid)) {
     765                 :          0 :         report_read_error(keypos);
     766                 :            :     }
     767         [ -  + ]:        197 :     if (newdid <= did) {
     768                 :            :         throw Xapian::DatabaseCorruptError("Document ID in new chunk of postlist (" +
     769                 :            :                 str(newdid) +
     770                 :            :                 ") is not greater than final document ID in previous chunk (" +
     771                 :          0 :                 str(did) + ")");
     772                 :            :     }
     773                 :        197 :     did = newdid;
     774                 :            : 
     775                 :        197 :     cursor->read_tag();
     776                 :        197 :     pos = cursor->current_tag.data();
     777                 :        197 :     end = pos + cursor->current_tag.size();
     778                 :            : 
     779                 :        197 :     first_did_in_chunk = did;
     780                 :            :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk,
     781                 :        197 :                                             &is_last_chunk);
     782                 :     118532 :     read_wdf(&pos, end, &wdf);
     783                 :            : }
     784                 :            : 
     785                 :            : PositionList *
     786                 :       2281 : ChertPostList::read_position_list()
     787                 :            : {
     788                 :            :     LOGCALL(DB, PositionList *, "ChertPostList::read_position_list", NO_ARGS);
     789                 :            :     Assert(this_db.get());
     790                 :       2281 :     positionlist.read_data(&this_db->position_table, did, term);
     791                 :       2281 :     RETURN(&positionlist);
     792                 :            : }
     793                 :            : 
     794                 :            : PositionList *
     795                 :      11391 : ChertPostList::open_position_list() const
     796                 :            : {
     797                 :            :     LOGCALL(DB, PositionList *, "ChertPostList::open_position_list", NO_ARGS);
     798                 :            :     Assert(this_db.get());
     799                 :      11391 :     RETURN(new ChertPositionList(&this_db->position_table, did, term));
     800                 :            : }
     801                 :            : 
     802                 :            : PostList *
     803                 :   19022220 : ChertPostList::next(Xapian::weight w_min)
     804                 :            : {
     805                 :            :     LOGCALL(DB, PostList *, "ChertPostList::next", w_min);
     806                 :            :     (void)w_min; // no warning
     807                 :            : 
     808         [ +  + ]:   19022220 :     if (!have_started) {
     809                 :     142268 :         have_started = true;
     810                 :            :     } else {
     811         [ +  + ]:   18879952 :         if (!next_in_chunk()) next_chunk();
     812                 :            :     }
     813                 :            : 
     814                 :   19022220 :     if (is_at_end) {
     815                 :            :         LOGLINE(DB, "Moved to end");
     816                 :            :     } else {
     817                 :            :         LOGLINE(DB, "Moved to docid " << did << ", wdf = " << wdf);
     818                 :            :     }
     819                 :            : 
     820                 :   19022220 :     RETURN(NULL);
     821                 :            : }
     822                 :            : 
     823                 :            : bool
     824                 :   19099495 : ChertPostList::current_chunk_contains(Xapian::docid desired_did)
     825                 :            : {
     826                 :            :     LOGCALL(DB, bool, "ChertPostList::current_chunk_contains", desired_did);
     827 [ +  + ][ +  + ]:   19099495 :     if (desired_did >= first_did_in_chunk &&
     828                 :            :         desired_did <= last_did_in_chunk) {
     829                 :   19098687 :         RETURN(true);
     830                 :            :     }
     831                 :   19099495 :     RETURN(false);
     832                 :            : }
     833                 :            : 
     834                 :            : void
     835                 :      83528 : ChertPostList::move_to_chunk_containing(Xapian::docid desired_did)
     836                 :            : {
     837                 :            :     LOGCALL_VOID(DB, "ChertPostList::move_to_chunk_containing", desired_did);
     838                 :      83528 :     (void)cursor->find_entry(ChertPostListTable::make_key(term, desired_did));
     839                 :            :     Assert(!cursor->after_end());
     840                 :            : 
     841                 :      83528 :     const char * keypos = cursor->current_key.data();
     842                 :      83528 :     const char * keyend = keypos + cursor->current_key.size();
     843                 :            :     // Check we're still in same postlist
     844         [ -  + ]:      83528 :     if (!check_tname_in_key_lite(&keypos, keyend, term)) {
     845                 :            :         // This should only happen if the postlist doesn't exist at all.
     846                 :          0 :         is_at_end = true;
     847                 :          0 :         is_last_chunk = true;
     848                 :          0 :         return;
     849                 :            :     }
     850                 :      83528 :     is_at_end = false;
     851                 :            : 
     852                 :      83528 :     cursor->read_tag();
     853                 :      83528 :     pos = cursor->current_tag.data();
     854                 :      83528 :     end = pos + cursor->current_tag.size();
     855                 :            : 
     856         [ +  + ]:      83528 :     if (keypos == keyend) {
     857                 :            :         // In first chunk
     858                 :            : #ifdef XAPIAN_ASSERTIONS
     859                 :            :         Xapian::doccount old_number_of_entries = number_of_entries;
     860                 :            :         did = read_start_of_first_chunk(&pos, end, &number_of_entries, NULL);
     861                 :            :         Assert(old_number_of_entries == number_of_entries);
     862                 :            : #else
     863                 :      83257 :         did = read_start_of_first_chunk(&pos, end, NULL, NULL);
     864                 :            : #endif
     865                 :            :     } else {
     866                 :            :         // In normal chunk
     867         [ -  + ]:        271 :         if (!unpack_uint_preserving_sort(&keypos, keyend, &did)) {
     868                 :          0 :             report_read_error(keypos);
     869                 :            :         }
     870                 :            :     }
     871                 :            : 
     872                 :      83528 :     first_did_in_chunk = did;
     873                 :            :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk,
     874                 :      83528 :                                             &is_last_chunk);
     875                 :      83528 :     read_wdf(&pos, end, &wdf);
     876                 :            : 
     877                 :            :     // Possible, since desired_did might be after end of this chunk and before
     878                 :            :     // the next.
     879         [ +  + ]:      83528 :     if (desired_did > last_did_in_chunk) next_chunk();
     880                 :            : }
     881                 :            : 
     882                 :            : bool
     883                 :   19099196 : ChertPostList::move_forward_in_chunk_to_at_least(Xapian::docid desired_did)
     884                 :            : {
     885                 :            :     LOGCALL(DB, bool, "ChertPostList::move_forward_in_chunk_to_at_least", desired_did);
     886         [ +  + ]:   19099196 :     if (did >= desired_did)
     887                 :     168009 :         RETURN(true);
     888                 :            : 
     889         [ +  - ]:   18931187 :     if (desired_did <= last_did_in_chunk) {
     890         [ +  - ]:   27067791 :         while (pos != end) {
     891                 :   27067791 :             read_did_increase(&pos, end, &did);
     892         [ +  + ]:   27067791 :             if (did >= desired_did) {
     893                 :   18931187 :                 read_wdf(&pos, end, &wdf);
     894                 :   18931187 :                 RETURN(true);
     895                 :            :             }
     896                 :            :             // It's faster to just skip over the wdf than to decode it.
     897                 :    8136604 :             read_wdf(&pos, end, NULL);
     898                 :            :         }
     899                 :            : 
     900                 :            :         // If we hit the end of the chunk then last_did_in_chunk must be wrong.
     901                 :            :         Assert(false);
     902                 :            :     }
     903                 :            : 
     904                 :          0 :     pos = end;
     905                 :   19099196 :     RETURN(false);
     906                 :            : }
     907                 :            : 
     908                 :            : PostList *
     909                 :       3430 : ChertPostList::skip_to(Xapian::docid desired_did, Xapian::weight w_min)
     910                 :            : {
     911                 :            :     LOGCALL(DB, PostList *, "ChertPostList::skip_to", desired_did | w_min);
     912                 :            :     (void)w_min; // no warning
     913                 :            :     // We've started now - if we hadn't already, we're already positioned
     914                 :            :     // at start so there's no need to actually do anything.
     915                 :       3430 :     have_started = true;
     916                 :            : 
     917                 :            :     // Don't skip back, and don't need to do anything if already there.
     918 [ +  + ][ +  + ]:       3430 :     if (is_at_end || desired_did <= did) RETURN(NULL);
     919                 :            : 
     920                 :            :     // Move to correct chunk
     921         [ +  + ]:       2900 :     if (!current_chunk_contains(desired_did)) {
     922                 :        290 :         move_to_chunk_containing(desired_did);
     923                 :            :         // Might be at_end now, so we need to check before trying to move
     924                 :            :         // forward in chunk.
     925         [ +  - ]:        290 :         if (is_at_end) RETURN(NULL);
     926                 :            :     }
     927                 :            : 
     928                 :            :     // Move to correct position in chunk
     929                 :       2610 :     bool have_document = move_forward_in_chunk_to_at_least(desired_did);
     930                 :            :     (void)have_document;
     931                 :            :     Assert(have_document);
     932                 :            : 
     933                 :       2610 :     if (is_at_end) {
     934                 :            :         LOGLINE(DB, "Skipped to end");
     935                 :            :     } else {
     936                 :            :         LOGLINE(DB, "Skipped to docid " << did << ", wdf = " << wdf);
     937                 :            :     }
     938                 :            : 
     939                 :       3430 :     RETURN(NULL);
     940                 :            : }
     941                 :            : 
     942                 :            : // Used for doclens.
     943                 :            : bool
     944                 :   19120662 : ChertPostList::jump_to(Xapian::docid desired_did)
     945                 :            : {
     946                 :            :     LOGCALL(DB, bool, "ChertPostList::jump_to", desired_did);
     947                 :            :     // We've started now - if we hadn't already, we're already positioned
     948                 :            :     // at start so there's no need to actually do anything.
     949                 :   19120662 :     have_started = true;
     950                 :            : 
     951                 :            :     // If the list is empty, give up right away.
     952         [ +  + ]:   19120662 :     if (pos == 0) RETURN(false);
     953                 :            : 
     954                 :            :     // Move to correct chunk, or reload the current chunk to go backwards in it
     955                 :            :     // (FIXME: perhaps handle the latter case more elegantly, though it won't
     956                 :            :     // happen during sequential access which is most common).
     957 [ +  + ][ +  + ]:   19096608 :     if (is_at_end || !current_chunk_contains(desired_did) || desired_did < did) {
         [ +  + ][ +  + ]
     958                 :            :         // Clear is_at_end flag since we can rewind.
     959                 :      83238 :         is_at_end = false;
     960                 :            : 
     961                 :      83238 :         move_to_chunk_containing(desired_did);
     962                 :            :         // Might be at_end now, so we need to check before trying to move
     963                 :            :         // forward in chunk.
     964         [ +  + ]:      83238 :         if (is_at_end) RETURN(false);
     965                 :            :     }
     966                 :            : 
     967                 :            :     // Move to correct position in chunk.
     968         [ -  + ]:   19096586 :     if (!move_forward_in_chunk_to_at_least(desired_did)) RETURN(false);
     969                 :   19120662 :     RETURN(desired_did == did);
     970                 :            : }
     971                 :            : 
     972                 :            : string
     973                 :          9 : ChertPostList::get_description() const
     974                 :            : {
     975                 :          9 :     return term + ":" + str(number_of_entries);
     976                 :            : }
     977                 :            : 
     978                 :            : // Returns the last did to allow in this chunk.
     979                 :            : Xapian::docid
     980                 :      21869 : ChertPostListTable::get_chunk(const string &tname,
     981                 :            :           Xapian::docid did, bool adding,
     982                 :            :           PostlistChunkReader ** from, PostlistChunkWriter **to)
     983                 :            : {
     984                 :            :     LOGCALL(DB, Xapian::docid, "ChertPostListTable::get_chunk", tname | did | adding | from | to);
     985                 :            :     // Get chunk containing entry
     986                 :      21869 :     string key = make_key(tname, did);
     987                 :            : 
     988                 :            :     // Find the right chunk
     989                 :      21869 :     AutoPtr<ChertCursor> cursor(cursor_get());
     990                 :            : 
     991                 :      21869 :     (void)cursor->find_entry(key);
     992                 :            :     Assert(!cursor->after_end());
     993                 :            : 
     994                 :      21869 :     const char * keypos = cursor->current_key.data();
     995                 :      21869 :     const char * keyend = keypos + cursor->current_key.size();
     996                 :            : 
     997         [ -  + ]:      21869 :     if (!check_tname_in_key(&keypos, keyend, tname)) {
     998                 :            :         // Postlist for this termname doesn't exist.
     999         [ #  # ]:          0 :         if (!adding)
    1000                 :          0 :             throw Xapian::DatabaseCorruptError("Attempted to delete or modify an entry in a non-existent posting list for " + tname);
    1001                 :            : 
    1002                 :          0 :         *from = NULL;
    1003                 :          0 :         *to = new PostlistChunkWriter(string(), true, tname, true);
    1004                 :          0 :         RETURN(Xapian::docid(-1));
    1005                 :            :     }
    1006                 :            : 
    1007                 :            :     // See if we're appending - if so we can shortcut by just copying
    1008                 :            :     // the data part of the chunk wholesale.
    1009                 :      21869 :     bool is_first_chunk = (keypos == keyend);
    1010                 :            :     LOGVALUE(DB, is_first_chunk);
    1011                 :            : 
    1012                 :      21869 :     cursor->read_tag();
    1013                 :      21869 :     const char * pos = cursor->current_tag.data();
    1014                 :      21869 :     const char * end = pos + cursor->current_tag.size();
    1015                 :            :     Xapian::docid first_did_in_chunk;
    1016         [ +  + ]:      21869 :     if (is_first_chunk) {
    1017                 :      21860 :         first_did_in_chunk = read_start_of_first_chunk(&pos, end, NULL, NULL);
    1018                 :            :     } else {
    1019         [ -  + ]:          9 :         if (!unpack_uint_preserving_sort(&keypos, keyend, &first_did_in_chunk)) {
    1020                 :          0 :             report_read_error(keypos);
    1021                 :            :         }
    1022                 :            :     }
    1023                 :            : 
    1024                 :            :     bool is_last_chunk;
    1025                 :            :     Xapian::docid last_did_in_chunk;
    1026                 :      21869 :     last_did_in_chunk = read_start_of_chunk(&pos, end, first_did_in_chunk, &is_last_chunk);
    1027                 :            :     *to = new PostlistChunkWriter(cursor->current_key, is_first_chunk, tname,
    1028                 :      21869 :                                   is_last_chunk);
    1029         [ +  + ]:      21869 :     if (did > last_did_in_chunk) {
    1030                 :            :         // This is the shortcut.  Not very pretty, but I'll leave refactoring
    1031                 :            :         // until I've a clearer picture of everything which needs to be done.
    1032                 :            :         // (FIXME)
    1033                 :      21715 :         *from = NULL;
    1034                 :            :         (*to)->raw_append(first_did_in_chunk, last_did_in_chunk,
    1035                 :      21715 :                           string(pos, end));
    1036                 :            :     } else {
    1037                 :        154 :         *from = new PostlistChunkReader(first_did_in_chunk, string(pos, end));
    1038                 :            :     }
    1039         [ +  + ]:      21869 :     if (is_last_chunk) RETURN(Xapian::docid(-1));
    1040                 :            : 
    1041                 :            :     // Find first did of next tag.
    1042                 :          6 :     cursor->next();
    1043         [ -  + ]:          6 :     if (cursor->after_end()) {
    1044                 :          0 :         throw Xapian::DatabaseCorruptError("Expected another key but found none");
    1045                 :            :     }
    1046                 :          6 :     const char *kpos = cursor->current_key.data();
    1047                 :          6 :     const char *kend = kpos + cursor->current_key.size();
    1048         [ -  + ]:          6 :     if (!check_tname_in_key(&kpos, kend, tname)) {
    1049                 :          0 :         throw Xapian::DatabaseCorruptError("Expected another key with the same term name but found a different one");
    1050                 :            :     }
    1051                 :            : 
    1052                 :            :     // Read the new first docid
    1053                 :            :     Xapian::docid first_did_of_next_chunk;
    1054         [ -  + ]:          6 :     if (!unpack_uint_preserving_sort(&kpos, kend, &first_did_of_next_chunk)) {
    1055                 :          0 :         report_read_error(kpos);
    1056                 :            :     }
    1057                 :      21869 :     RETURN(first_did_of_next_chunk - 1);
    1058                 :            : }
    1059                 :            : 
    1060                 :            : void
    1061                 :        482 : ChertPostListTable::merge_changes(
    1062                 :            :     const map<string, map<Xapian::docid, pair<char, Xapian::termcount> > > & mod_plists,
    1063                 :            :     const map<Xapian::docid, Xapian::termcount> & doclens,
    1064                 :            :     const map<string, pair<Xapian::termcount_diff, Xapian::termcount_diff> > & freq_deltas)
    1065                 :            : {
    1066                 :            :     LOGCALL_VOID(DB, "ChertPostListTable::merge_changes", mod_plists | doclens | freq_deltas);
    1067                 :            : 
    1068                 :            :     // The cursor in the doclen_pl will no longer be valid, so reset it.
    1069                 :        482 :     doclen_pl.reset(0);
    1070                 :            : 
    1071                 :            :     LOGVALUE(DB, doclens.size());
    1072         [ +  + ]:        482 :     if (!doclens.empty()) {
    1073                 :            :         // Ensure there's a first chunk.
    1074                 :        439 :         string current_key = make_key(string());
    1075         [ +  + ]:        439 :         if (!key_exists(current_key)) {
    1076                 :            :             LOGLINE(DB, "Adding dummy first chunk");
    1077                 :        266 :             string newtag = make_start_of_first_chunk(0, 0, 0);
    1078                 :        266 :             newtag += make_start_of_chunk(true, 0, 0);
    1079                 :        266 :             add(current_key, newtag);
    1080                 :            :         }
    1081                 :            : 
    1082                 :        439 :         map<Xapian::docid, Xapian::termcount>::const_iterator j;
    1083                 :        439 :         j = doclens.begin();
    1084                 :            :         Assert(j != doclens.end()); // This case is caught above.
    1085                 :            : 
    1086                 :            :         Xapian::docid max_did;
    1087                 :            :         PostlistChunkReader *from;
    1088                 :            :         PostlistChunkWriter *to;
    1089                 :        439 :         max_did = get_chunk(string(), j->first, true, &from, &to);
    1090                 :            :         LOGVALUE(DB, max_did);
    1091         [ +  + ]:      78505 :         for ( ; j != doclens.end(); ++j) {
    1092                 :      78066 :             Xapian::docid did = j->first;
    1093                 :            : 
    1094                 :            : next_doclen_chunk:
    1095                 :            :             LOGLINE(DB, "Updating doclens, did=" << did);
    1096 [ +  + ][ +  + ]:      80144 :             if (from) while (!from->is_at_end()) {
    1097                 :      11980 :                 Xapian::docid copy_did = from->get_docid();
    1098         [ +  + ]:      11980 :                 if (copy_did >= did) {
    1099         [ +  + ]:       9908 :                     if (copy_did == did) from->next();
    1100                 :       9908 :                     break;
    1101                 :            :                 }
    1102                 :       2072 :                 to->append(this, copy_did, from->get_wdf());
    1103                 :       2072 :                 from->next();
    1104                 :            :             }
    1105 [ +  + ][ +  + ]:      78072 :             if ((!from || from->is_at_end()) && did > max_did) {
         [ +  + ][ +  + ]
    1106         [ +  - ]:          6 :                 delete from;
    1107                 :          6 :                 to->flush(this);
    1108         [ +  - ]:          6 :                 delete to;
    1109                 :          6 :                 max_did = get_chunk(string(), did, false, &from, &to);
    1110                 :          6 :                 goto next_doclen_chunk;
    1111                 :            :             }
    1112                 :            : 
    1113                 :      78066 :             Xapian::termcount new_doclen = j->second;
    1114         [ +  + ]:      78066 :             if (new_doclen != static_cast<Xapian::termcount>(-1)) {
    1115                 :      68187 :                 to->append(this, did, new_doclen);
    1116                 :            :             }
    1117                 :            :         }
    1118                 :            : 
    1119         [ +  + ]:        439 :         if (from) {
    1120         [ +  + ]:         99 :             while (!from->is_at_end()) {
    1121                 :          3 :                 to->append(this, from->get_docid(), from->get_wdf());
    1122                 :          3 :                 from->next();
    1123                 :            :             }
    1124         [ +  - ]:         96 :             delete from;
    1125                 :            :         }
    1126                 :        439 :         to->flush(this);
    1127         [ +  - ]:        439 :         delete to;
    1128                 :            :     }
    1129                 :            : 
    1130                 :        482 :     map<string, map<Xapian::docid, pair<char, Xapian::termcount> > >::const_iterator i;
    1131         [ +  + ]:      22031 :     for (i = mod_plists.begin(); i != mod_plists.end(); ++i) {
    1132         [ -  + ]:      21549 :         if (i->second.empty()) continue;
    1133                 :      21549 :         string tname = i->first;
    1134                 :            :         {
    1135                 :            :             // Rewrite the first chunk of this posting list with the updated
    1136                 :            :             // termfreq and collfreq.
    1137                 :      21549 :             map<string, pair<Xapian::termcount_diff, Xapian::termcount_diff> >::const_iterator deltas = freq_deltas.find(tname);
    1138                 :            :             Assert(deltas != freq_deltas.end());
    1139                 :            : 
    1140                 :      21549 :             string current_key = make_key(tname);
    1141                 :      21549 :             string tag;
    1142                 :      21549 :             (void)get_exact_entry(current_key, tag);
    1143                 :            : 
    1144                 :            :             // Read start of first chunk to get termfreq and collfreq.
    1145                 :      21549 :             const char *pos = tag.data();
    1146                 :      21549 :             const char *end = pos + tag.size();
    1147                 :            :             Xapian::doccount termfreq;
    1148                 :            :             Xapian::termcount collfreq;
    1149                 :            :             Xapian::docid firstdid, lastdid;
    1150                 :            :             bool islast;
    1151         [ +  + ]:      21549 :             if (pos == end) {
    1152                 :      21148 :                 termfreq = 0;
    1153                 :      21148 :                 collfreq = 0;
    1154                 :      21148 :                 firstdid = 0;
    1155                 :      21148 :                 lastdid = 0;
    1156                 :      21148 :                 islast = true;
    1157                 :            :             } else {
    1158                 :            :                 firstdid = read_start_of_first_chunk(&pos, end,
    1159                 :        401 :                                                      &termfreq, &collfreq);
    1160                 :            :                 // Handle the generic start of chunk header.
    1161                 :        401 :                 lastdid = read_start_of_chunk(&pos, end, firstdid, &islast);
    1162                 :            :             }
    1163                 :            : 
    1164                 :      21549 :             termfreq += deltas->second.first;
    1165         [ +  + ]:      21549 :             if (termfreq == 0) {
    1166                 :            :                 // All postings deleted!  So we can shortcut by zapping the
    1167                 :            :                 // posting list.
    1168         [ +  + ]:        117 :                 if (islast) {
    1169                 :            :                     // Only one entry for this posting list.
    1170                 :        111 :                     del(current_key);
    1171                 :        111 :                     continue;
    1172                 :            :                 }
    1173                 :          6 :                 MutableChertCursor cursor(this);
    1174                 :          6 :                 bool found = cursor.find_entry(current_key);
    1175                 :            :                 Assert(found);
    1176         [ -  + ]:          6 :                 if (!found) continue; // Reduce damage!
    1177         [ +  + ]:         18 :                 while (cursor.del()) {
    1178                 :         15 :                     const char *kpos = cursor.current_key.data();
    1179                 :         15 :                     const char *kend = kpos + cursor.current_key.size();
    1180         [ +  + ]:         15 :                     if (!check_tname_in_key_lite(&kpos, kend, tname)) break;
    1181                 :            :                 }
    1182                 :          6 :                 continue;
    1183                 :            :             }
    1184                 :      21432 :             collfreq += deltas->second.second;
    1185                 :            : 
    1186                 :            :             // Rewrite start of first chunk to update termfreq and collfreq.
    1187                 :      21432 :             string newhdr = make_start_of_first_chunk(termfreq, collfreq, firstdid);
    1188                 :      21432 :             newhdr += make_start_of_chunk(islast, firstdid, lastdid);
    1189         [ +  + ]:      21432 :             if (pos == end) {
    1190                 :      21122 :                 add(current_key, newhdr);
    1191                 :            :             } else {
    1192                 :            :                 Assert((size_t)(pos - tag.data()) <= tag.size());
    1193                 :        318 :                 tag.replace(0, pos - tag.data(), newhdr);
    1194                 :        318 :                 add(current_key, tag);
    1195 [ +  + ][ +  + ]:      21565 :             }
    1196                 :            :         }
    1197                 :      21424 :         map<Xapian::docid, pair<char, Xapian::termcount> >::const_iterator j;
    1198                 :      21424 :         j = i->second.begin();
    1199                 :            :         Assert(j != i->second.end()); // This case is caught above.
    1200                 :            : 
    1201                 :            :         Xapian::docid max_did;
    1202                 :            :         PostlistChunkReader *from;
    1203                 :            :         PostlistChunkWriter *to;
    1204                 :            :         max_did = get_chunk(tname, j->first, j->second.first == 'A',
    1205                 :      21424 :                             &from, &to);
    1206         [ +  + ]:     798098 :         for ( ; j != i->second.end(); ++j) {
    1207                 :     776674 :             Xapian::docid did = j->first;
    1208                 :            : 
    1209                 :            : next_chunk:
    1210                 :            :             LOGLINE(DB, "Updating tname=" << tname << ", did=" << did);
    1211 [ +  + ][ +  - ]:     784824 :             if (from) while (!from->is_at_end()) {
    1212                 :       8234 :                 Xapian::docid copy_did = from->get_docid();
    1213         [ +  + ]:       8234 :                 if (copy_did >= did) {
    1214         [ +  + ]:         84 :                     if (copy_did == did) {
    1215                 :            :                         Assert(j->second.first != 'A');
    1216                 :         81 :                         from->next();
    1217                 :            :                     }
    1218                 :         84 :                     break;
    1219                 :            :                 }
    1220                 :       8150 :                 to->append(this, copy_did, from->get_wdf());
    1221                 :       8150 :                 from->next();
    1222                 :            :             }
    1223 [ +  + ][ +  + ]:     776674 :             if ((!from || from->is_at_end()) && did > max_did) {
         [ -  + ][ -  + ]
    1224         [ #  # ]:          0 :                 delete from;
    1225                 :          0 :                 to->flush(this);
    1226         [ #  # ]:          0 :                 delete to;
    1227                 :          0 :                 max_did = get_chunk(tname, did, false, &from, &to);
    1228                 :          0 :                 goto next_chunk;
    1229                 :            :             }
    1230                 :            : 
    1231         [ +  + ]:     776674 :             if (j->second.first != 'D') {
    1232                 :     776482 :                 Xapian::termcount new_wdf = j->second.second;
    1233                 :     776482 :                 to->append(this, did, new_wdf);
    1234                 :            :             }
    1235                 :            :         }
    1236                 :            : 
    1237         [ +  + ]:      21424 :         if (from) {
    1238         [ +  + ]:         55 :             while (!from->is_at_end()) {
    1239                 :          3 :                 to->append(this, from->get_docid(), from->get_wdf());
    1240                 :          3 :                 from->next();
    1241                 :            :             }
    1242         [ +  - ]:         52 :             delete from;
    1243                 :            :         }
    1244                 :      21424 :         to->flush(this);
    1245         [ +  - ]:      21424 :         delete to;
    1246                 :            :     }
    1247                 :        474 : }

Generated by: LCOV version 1.8