LCOV - code coverage report
Current view: top level - api - queryinternal.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core 7028d852e609 Lines: 896 994 90.1 %
Date: 2019-02-17 14:59:59 Functions: 147 161 91.3 %
Branches: 754 1422 53.0 %

           Branch data     Line data    Source code
       1                 :            : /** @file queryinternal.cc
       2                 :            :  * @brief Xapian::Query internals
       3                 :            :  */
       4                 :            : /* Copyright (C) 2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018 Olly Betts
       5                 :            :  * Copyright (C) 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 USA
      20                 :            :  */
      21                 :            : 
      22                 :            : #include <config.h>
      23                 :            : 
      24                 :            : #include "queryinternal.h"
      25                 :            : 
      26                 :            : #include "xapian/error.h"
      27                 :            : #include "xapian/postingsource.h"
      28                 :            : #include "xapian/query.h"
      29                 :            : 
      30                 :            : #include "heap.h"
      31                 :            : #include "leafpostlist.h"
      32                 :            : #include "matcher/andmaybepostlist.h"
      33                 :            : #include "matcher/andnotpostlist.h"
      34                 :            : #include "matcher/boolorpostlist.h"
      35                 :            : #include "emptypostlist.h"
      36                 :            : #include "matcher/exactphrasepostlist.h"
      37                 :            : #include "matcher/externalpostlist.h"
      38                 :            : #include "matcher/maxpostlist.h"
      39                 :            : #include "matcher/multiandpostlist.h"
      40                 :            : #include "matcher/multixorpostlist.h"
      41                 :            : #include "matcher/nearpostlist.h"
      42                 :            : #include "matcher/orpospostlist.h"
      43                 :            : #include "matcher/orpostlist.h"
      44                 :            : #include "matcher/phrasepostlist.h"
      45                 :            : #include "matcher/queryoptimiser.h"
      46                 :            : #include "matcher/valuerangepostlist.h"
      47                 :            : #include "matcher/valuegepostlist.h"
      48                 :            : #include "net/length.h"
      49                 :            : #include "serialise-double.h"
      50                 :            : #include "stringutils.h"
      51                 :            : #include "termlist.h"
      52                 :            : 
      53                 :            : #include "debuglog.h"
      54                 :            : #include "omassert.h"
      55                 :            : #include "str.h"
      56                 :            : #include "stringutils.h"
      57                 :            : #include "unicode/description_append.h"
      58                 :            : 
      59                 :            : #include <algorithm>
      60                 :            : #include <functional>
      61                 :            : #include <list>
      62                 :            : #include <memory>
      63                 :            : #include <string>
      64                 :            : #include <unordered_set>
      65                 :            : #include <vector>
      66                 :            : 
      67                 :            : using namespace std;
      68                 :            : 
      69                 :            : static constexpr unsigned MAX_UTF_8_CHARACTER_LENGTH = 4;
      70                 :            : 
      71                 :            : template<class CLASS> struct delete_ptr {
      72                 :            :     void operator()(CLASS *p) const { delete p; }
      73                 :            : };
      74                 :            : 
      75                 :            : using Xapian::Internal::AndContext;
      76                 :            : using Xapian::Internal::OrContext;
      77                 :            : using Xapian::Internal::BoolOrContext;
      78                 :            : using Xapian::Internal::XorContext;
      79                 :            : 
      80                 :            : namespace Xapian {
      81                 :            : 
      82                 :            : namespace Internal {
      83                 :            : 
      84                 :            : struct PostListAndTermFreq {
      85                 :            :     PostList* pl;
      86                 :            :     Xapian::doccount tf = 0;
      87                 :            : 
      88                 :          0 :     PostListAndTermFreq() : pl(nullptr) {}
      89                 :            : 
      90                 :            :     explicit
      91                 :     285396 :     PostListAndTermFreq(PostList* pl_) : pl(pl_) {}
      92                 :            : };
      93                 :            : 
      94                 :            : /** Class providing an operator which sorts postlists to select max or terms.
      95                 :            :  *  This returns true if a has a (strictly) greater termweight than b.
      96                 :            :  *
      97                 :            :  *  Using this comparator will tend to result in multiple calls to
      98                 :            :  *  recalc_maxweight() for each of the subqueries (we use it with nth_element
      99                 :            :  *  which should be O(n)) - perhaps it'd be better to call recalc_maxweight()
     100                 :            :  *  once and then sort on that.
     101                 :            :  */
     102                 :            : struct CmpMaxOrTerms {
     103                 :            :     /** Return true if and only if a has a strictly greater termweight than b. */
     104                 :      11528 :     bool operator()(const PostListAndTermFreq& elt_a,
     105                 :            :                     const PostListAndTermFreq& elt_b) {
     106                 :      11528 :         PostList* a = elt_a.pl;
     107                 :      11528 :         PostList* b = elt_b.pl;
     108                 :            : #if (defined(__i386__) && !defined(__SSE_MATH__)) || \
     109                 :            :     defined(__mc68000__) || defined(__mc68010__) || \
     110                 :            :     defined(__mc68020__) || defined(__mc68030__)
     111                 :            :         // On some architectures, most common of which is x86, floating point
     112                 :            :         // values are calculated and stored in registers with excess precision.
     113                 :            :         // If the two recalc_maxweight() calls below return identical values in a
     114                 :            :         // register, the excess precision may be dropped for one of them but
     115                 :            :         // not the other (e.g. because the compiler saves the first calculated
     116                 :            :         // weight to memory while calculating the second, then reloads it to
     117                 :            :         // compare).  This leads to both a > b and b > a being true, which
     118                 :            :         // violates the antisymmetry property of the strict weak ordering
     119                 :            :         // required by nth_element().  This can have serious consequences (e.g.
     120                 :            :         // segfaults).
     121                 :            :         //
     122                 :            :         // Note that m68k only has excess precision in earlier models - 68040
     123                 :            :         // and later are OK:
     124                 :            :         // https://gcc.gnu.org/ml/gcc-patches/2008-11/msg00105.html
     125                 :            :         //
     126                 :            :         // To avoid this, we store each result in a volatile double prior to
     127                 :            :         // comparing them.  This means that the result of this test should
     128                 :            :         // match that on other architectures with the same double format (which
     129                 :            :         // is desirable), and actually has less overhead than rounding both
     130                 :            :         // results to float (which is another approach which works).
     131                 :            :         volatile double a_max_wt = a->recalc_maxweight();
     132                 :            :         volatile double b_max_wt = b->recalc_maxweight();
     133                 :            :         return a_max_wt > b_max_wt;
     134                 :            : #else
     135                 :      11528 :         return a->recalc_maxweight() > b->recalc_maxweight();
     136                 :            : #endif
     137                 :            :     }
     138                 :            : };
     139                 :            : 
     140                 :            : /// Comparison functor which orders by descending termfreq.
     141                 :            : struct ComparePostListTermFreqAscending {
     142                 :            :     /// Order PostListAndTermFreq by descending tf.
     143                 :     161352 :     bool operator()(const PostListAndTermFreq& a,
     144                 :            :                     const PostListAndTermFreq& b) const {
     145                 :     161352 :         return a.tf > b.tf;
     146                 :            :     }
     147                 :            : 
     148                 :            :     /// Order PostList* by descending get_termfreq_est().
     149                 :        630 :     bool operator()(const PostList* a,
     150                 :            :                     const PostList* b) const {
     151                 :        630 :         return a->get_termfreq_est() > b->get_termfreq_est();
     152                 :            :     }
     153                 :            : };
     154                 :            : 
     155                 :            : template<typename T>
     156                 :            : class Context {
     157                 :            :     /** Helper for initialisation when T = PostList*.
     158                 :            :      *
     159                 :            :      *  No initialisation is needed for this case.
     160                 :            :      */
     161                 :         24 :     void init_tf_(vector<PostList*>&) { }
     162                 :            : 
     163                 :            :     /** Helper for initialisation when T = PostListAndTermFreq. */
     164                 :     140229 :     void init_tf_(vector<PostListAndTermFreq>&) {
     165 [ +  - ][ -  + ]:     280458 :         if (pls.empty() || pls.front().tf != 0) return;
                 [ -  + ]
     166         [ +  + ]:     423173 :         for (auto&& elt : pls) {
     167         [ +  - ]:     282944 :             elt.tf = elt.pl->get_termfreq_est();
     168                 :            :         }
     169                 :            :     }
     170                 :            : 
     171                 :            :   protected:
     172                 :            :     QueryOptimiser* qopt;
     173                 :            : 
     174                 :            :     vector<T> pls;
     175                 :            : 
     176                 :            :     /** Helper for initialisation.
     177                 :            :      *
     178                 :            :      *  Use with BoolOrContext and OrContext.
     179                 :            :      */
     180                 :     280506 :     void init_tf() { init_tf_(pls); }
     181                 :            : 
     182                 :            :     /** Helper for dereferencing when T = PostList*. */
     183                 :        648 :     PostList* as_postlist(PostList* pl) { return pl; }
     184                 :            : 
     185                 :            :     /** Helper for dereferencing when T = PostListAndTermFreq. */
     186                 :       4480 :     PostList* as_postlist(const PostListAndTermFreq& x) { return x.pl; }
     187                 :            : 
     188                 :            :   public:
     189                 :     285100 :     Context(QueryOptimiser* qopt_, size_t reserve) : qopt(qopt_) {
     190   [ +  -  +  - ]:     142550 :         pls.reserve(reserve);
     191                 :     142550 :     }
     192                 :            : 
     193                 :     142550 :     ~Context() {
     194                 :     142550 :         shrink(0);
     195                 :     142550 :     }
     196                 :            : 
     197                 :     290493 :     void add_postlist(PostList * pl) {
     198                 :     290493 :         pls.emplace_back(pl);
     199                 :     290493 :     }
     200                 :            : 
     201                 :        192 :     bool empty() const {
     202                 :        192 :         return pls.empty();
     203                 :            :     }
     204                 :            : 
     205                 :         24 :     size_t size() const {
     206                 :         24 :         return pls.size();
     207                 :            :     }
     208                 :            : 
     209                 :     142798 :     void shrink(size_t new_size) {
     210                 :            :         AssertRel(new_size, <=, pls.size());
     211 [ +  + ][ +  + ]:     142798 :         if (new_size >= pls.size())
     212                 :     142531 :             return;
     213                 :            : 
     214                 :        267 :         const PostList * hint_pl = qopt->get_hint_postlist();
     215 [ +  + ][ +  + ]:       2831 :         for (auto&& i = pls.begin() + new_size; i != pls.end(); ++i) {
     216                 :       2564 :             const PostList * pl = as_postlist(*i);
     217   [ +  +  +  + ]:       2564 :             if (rare(pl == hint_pl)) {
     218                 :            :                 // We were about to delete qopt's hint - instead tell qopt to
     219                 :            :                 // take ownership.
     220                 :        147 :                 qopt->take_hint_ownership();
     221                 :        147 :                 hint_pl = NULL;
     222                 :            :             } else {
     223 [ +  - ][ +  - ]:       2417 :                 delete pl;
     224                 :            :             }
     225                 :            :         }
     226                 :        267 :         pls.resize(new_size);
     227                 :            :     }
     228                 :            : 
     229                 :            :     /** Expand a wildcard query.
     230                 :            :      *
     231                 :            :      *  Used with BoolOrContext and OrContext.
     232                 :            :      */
     233                 :            :     void expand_wildcard(const QueryWildcard* query, double factor);
     234                 :            : };
     235                 :            : 
     236                 :            : template<typename T>
     237                 :            : inline void
     238                 :        223 : Context<T>::expand_wildcard(const QueryWildcard* query,
     239                 :            :                             double factor)
     240                 :            : {
     241 [ #  # ][ #  # ]:        223 :     unique_ptr<TermList> t(qopt->db.open_allterms(query->get_fixed_prefix()));
         [ +  - ][ +  - ]
     242 [ #  # ][ +  - ]:        223 :     bool skip_ucase = query->get_fixed_prefix().empty();
     243                 :        223 :     auto max_type = query->get_max_type();
     244                 :        223 :     Xapian::termcount expansions_left = query->get_max_expansion();
     245                 :            :     // If there's no expansion limit, set expansions_left to the maximum
     246                 :            :     // value Xapian::termcount can hold.
     247   [ #  #  +  + ]:        223 :     if (expansions_left == 0)
     248                 :        114 :         --expansions_left;
     249                 :        865 :     while (true) {
     250 [ #  # ][ +  - ]:       1057 :         t->next();
     251 [ #  # ][ #  # ]:       1057 :         if (t->at_end())
         [ +  - ][ +  + ]
     252                 :        186 :             break;
     253                 :            : 
     254 [ #  # ][ +  - ]:        871 :         const string & term = t->get_termname();
     255 [ #  # ][ #  # ]:        871 :         if (skip_ucase && term[0] >= 'A') {
         [ #  # ][ +  + ]
         [ +  - ][ +  + ]
     256                 :            :             // If there's a leading wildcard then skip terms that start
     257                 :            :             // with A-Z, as we don't want the expansion to include prefixed
     258                 :            :             // terms.
     259                 :            :             //
     260                 :            :             // This assumes things about the structure of terms which the
     261                 :            :             // Query class otherwise doesn't need to care about, but it
     262                 :            :             // seems hard to avoid here.
     263                 :         12 :             skip_ucase = false;
     264 [ #  # ][ -  + ]:         12 :             if (term[0] <= 'Z') {
     265                 :            :                 static_assert('Z' + 1 == '[', "'Z' + 1 == '['");
     266 [ #  # ][ #  # ]:          0 :                 t->skip_to("[");
         [ #  # ][ #  # ]
     267                 :          0 :                 continue;
     268                 :            :             }
     269                 :            :         }
     270                 :            : 
     271 [ #  # ][ #  # ]:        871 :         if (!query->test_prefix_known(term)) continue;
         [ +  - ][ +  + ]
     272                 :            : 
     273 [ #  # ][ +  + ]:        806 :         if (max_type < Xapian::Query::WILDCARD_LIMIT_MOST_FREQUENT) {
     274 [ #  # ][ +  + ]:        554 :             if (expansions_left-- == 0) {
     275 [ #  # ][ +  + ]:         37 :                 if (max_type == Xapian::Query::WILDCARD_LIMIT_FIRST)
     276                 :          6 :                     break;
     277 [ #  # ][ +  - ]:         31 :                 string msg("Wildcard ");
     278 [ #  # ][ #  # ]:         31 :                 msg += query->get_pattern();
         [ +  - ][ +  - ]
     279   [ #  #  +  - ]:         31 :                 if (query->get_just_flags() == 0)
     280 [ #  # ][ +  - ]:         31 :                     msg += '*';
     281 [ #  # ][ +  - ]:         31 :                 msg += " expands to more than ";
     282 [ #  # ][ #  # ]:         31 :                 msg += str(query->get_max_expansion());
         [ +  - ][ +  - ]
     283   [ #  #  +  - ]:         31 :                 msg += " terms";
     284 [ #  # ][ #  # ]:        554 :                 throw Xapian::WildcardError(msg);
         [ +  - ][ +  - ]
     285                 :            :             }
     286                 :            :         }
     287                 :            : 
     288 [ #  # ][ #  # ]:        840 :         add_postlist(qopt->open_lazy_post_list(term, 1, factor));
              [ #  #  # ]
         [ +  - ][ +  - ]
              [ +  +  + ]
     289                 :            :     }
     290                 :            : 
     291 [ #  # ][ +  + ]:        192 :     if (max_type == Xapian::Query::WILDCARD_LIMIT_MOST_FREQUENT) {
     292                 :            :         // FIXME: open_lazy_post_list() results in the term getting registered
     293                 :            :         // for stats, so we still incur an avoidable cost from the full
     294                 :            :         // expansion size of the wildcard, which is most likely to be visible
     295                 :            :         // with the remote backend.  Perhaps we should split creating the lazy
     296                 :            :         // postlist from registering the term for stats.
     297                 :         24 :         auto set_size = query->get_max_expansion();
     298   [ #  #  +  - ]:         24 :         if (size() > set_size) {
     299 [ #  # ][ +  - ]:         24 :             init_tf();
     300                 :         24 :             auto begin = pls.begin();
     301   [ #  #  +  - ]:         24 :             nth_element(begin, begin + set_size - 1, pls.end(),
     302                 :         24 :                         ComparePostListTermFreqAscending());
     303 [ #  # ][ +  - ]:         24 :             shrink(set_size);
     304                 :            :         }
     305                 :        223 :     }
     306                 :        192 : }
     307                 :            : 
     308                 :       1746 : class BoolOrContext : public Context<PostList*> {
     309                 :            :   public:
     310                 :        873 :     BoolOrContext(QueryOptimiser* qopt_, size_t reserve)
     311                 :        873 :         : Context(qopt_, reserve) { }
     312                 :            : 
     313                 :            :     PostList * postlist();
     314                 :            : };
     315                 :            : 
     316                 :            : PostList *
     317                 :        788 : BoolOrContext::postlist()
     318                 :            : {
     319                 :            :     Assert(!pls.empty());
     320                 :            : 
     321                 :            :     PostList* pl;
     322         [ +  + ]:        788 :     if (pls.size() == 1) {
     323                 :         30 :         pl = pls[0];
     324                 :            :     } else {
     325 [ +  - ][ +  - ]:        758 :         pl = new BoolOrPostList(pls.begin(), pls.end(), qopt->db_size);
     326                 :            :     }
     327                 :            : 
     328                 :            :     // Empty pls so our destructor doesn't delete them all!
     329                 :        788 :     pls.clear();
     330                 :        788 :     return pl;
     331                 :            : }
     332                 :            : 
     333                 :     280882 : class OrContext : public Context<PostListAndTermFreq> {
     334                 :            :   public:
     335                 :     140441 :     OrContext(QueryOptimiser* qopt_, size_t reserve)
     336                 :     140441 :         : Context(qopt_, reserve) { }
     337                 :            : 
     338                 :            :     /// Select the best set_size postlists from the last out_of added.
     339                 :            :     void select_elite_set(size_t set_size, size_t out_of);
     340                 :            : 
     341                 :            :     PostList * postlist();
     342                 :            :     PostList * postlist_max();
     343                 :            : };
     344                 :            : 
     345                 :            : void
     346                 :        208 : OrContext::select_elite_set(size_t set_size, size_t out_of)
     347                 :            : {
     348                 :        208 :     auto begin = pls.begin() + pls.size() - out_of;
     349         [ +  - ]:        208 :     nth_element(begin, begin + set_size - 1, pls.end(), CmpMaxOrTerms());
     350         [ +  - ]:        208 :     shrink(pls.size() - out_of + set_size);
     351                 :        208 : }
     352                 :            : 
     353                 :            : PostList *
     354                 :     140433 : OrContext::postlist()
     355                 :            : {
     356                 :            :     Assert(!pls.empty());
     357                 :            : 
     358         [ +  + ]:     140433 :     if (pls.size() == 1) {
     359                 :        212 :         PostList * pl = pls[0].pl;
     360                 :        212 :         pls.clear();
     361                 :        212 :         return pl;
     362                 :            :     }
     363                 :            : 
     364                 :            :     // Make postlists into a heap so that the postlist with the greatest term
     365                 :            :     // frequency is at the top of the heap.
     366                 :     140221 :     init_tf();
     367         [ +  - ]:     140221 :     Heap::make(pls.begin(), pls.end(), ComparePostListTermFreqAscending());
     368                 :            : 
     369                 :            :     // Now build a tree of binary OrPostList objects.
     370                 :            :     //
     371                 :            :     // The algorithm used to build the tree is like that used to build an
     372                 :            :     // optimal Huffman coding tree.  If we called next() repeatedly, this
     373                 :            :     // arrangement would minimise the number of method calls.  Generally we
     374                 :            :     // don't actually do that, but this arrangement is still likely to be a
     375                 :            :     // good one, and it does minimise the work in the worst case.
     376                 :            :     while (true) {
     377                 :            :         // We build the tree such that at each branch:
     378                 :            :         //
     379                 :            :         //   l.get_termfreq_est() >= r.get_termfreq_est()
     380                 :            :         //
     381                 :            :         // We do this so that the OrPostList class can be optimised assuming
     382                 :            :         // that this is the case.
     383                 :     142707 :         PostList * r = pls.front().pl;
     384                 :     142707 :         auto tf = pls.front().tf;
     385         [ +  - ]:     142707 :         Heap::pop(pls.begin(), pls.end(), ComparePostListTermFreqAscending());
     386                 :     142707 :         pls.pop_back();
     387                 :            :         PostList * pl;
     388                 :     142707 :         pl = new OrPostList(pls.front().pl, r, qopt->matcher, qopt->db_size);
     389                 :            : 
     390         [ +  + ]:     142707 :         if (pls.size() == 1) {
     391                 :     140221 :             pls.clear();
     392                 :     140221 :             return pl;
     393                 :            :         }
     394                 :            : 
     395                 :       2486 :         pls[0].pl = pl;
     396                 :       2486 :         pls[0].tf += tf;
     397                 :            :         Heap::replace(pls.begin(), pls.end(),
     398         [ +  - ]:       2486 :                       ComparePostListTermFreqAscending());
     399                 :     142919 :     }
     400                 :            : }
     401                 :            : 
     402                 :            : PostList *
     403                 :          8 : OrContext::postlist_max()
     404                 :            : {
     405                 :            :     Assert(!pls.empty());
     406                 :            : 
     407         [ -  + ]:          8 :     if (pls.size() == 1) {
     408                 :          0 :         PostList * pl = pls[0].pl;
     409                 :          0 :         pls.clear();
     410                 :          0 :         return pl;
     411                 :            :     }
     412                 :            : 
     413                 :            :     // Sort the postlists so that the postlist with the greatest term frequency
     414                 :            :     // is first.
     415                 :          8 :     init_tf();
     416         [ +  - ]:          8 :     sort(pls.begin(), pls.end(), ComparePostListTermFreqAscending());
     417                 :            : 
     418                 :            :     PostList * pl;
     419 [ +  - ][ +  - ]:          8 :     pl = new MaxPostList(pls.begin(), pls.end(), qopt->matcher, qopt->db_size);
     420                 :            : 
     421                 :          8 :     pls.clear();
     422                 :          8 :     return pl;
     423                 :            : }
     424                 :            : 
     425                 :        224 : class XorContext : public Context<PostList*> {
     426                 :            :   public:
     427                 :        112 :     XorContext(QueryOptimiser* qopt_, size_t reserve)
     428                 :        112 :         : Context(qopt_, reserve) { }
     429                 :            : 
     430                 :            :     PostList * postlist();
     431                 :            : };
     432                 :            : 
     433                 :            : PostList *
     434                 :        112 : XorContext::postlist()
     435                 :            : {
     436                 :        112 :     Xapian::doccount db_size = qopt->db_size;
     437                 :            :     PostList * pl;
     438 [ +  - ][ +  - ]:        112 :     pl = new MultiXorPostList(pls.begin(), pls.end(), qopt->matcher, db_size);
     439                 :            : 
     440                 :            :     // Empty pls so our destructor doesn't delete them all!
     441                 :        112 :     pls.clear();
     442                 :        112 :     return pl;
     443                 :            : }
     444                 :            : 
     445                 :       2248 : class AndContext : public Context<PostList*> {
     446                 :            :     class PosFilter {
     447                 :            :         Xapian::Query::op op_;
     448                 :            : 
     449                 :            :         /// Start and end indices for the PostLists this positional filter uses.
     450                 :            :         size_t begin, end;
     451                 :            : 
     452                 :            :         Xapian::termcount window;
     453                 :            : 
     454                 :            :       public:
     455                 :        688 :         PosFilter(Xapian::Query::op op__, size_t begin_, size_t end_,
     456                 :            :                   Xapian::termcount window_)
     457                 :        688 :             : op_(op__), begin(begin_), end(end_), window(window_) { }
     458                 :            : 
     459                 :            :         PostList * postlist(PostList* pl,
     460                 :            :                             const vector<PostList*>& pls,
     461                 :            :                             PostListTree* pltree) const;
     462                 :            :     };
     463                 :            : 
     464                 :            :     list<PosFilter> pos_filters;
     465                 :            : 
     466                 :            :   public:
     467                 :       1124 :     AndContext(QueryOptimiser* qopt_, size_t reserve)
     468                 :       1124 :         : Context(qopt_, reserve) { }
     469                 :            : 
     470                 :            :     void add_pos_filter(Query::op op_,
     471                 :            :                         size_t n_subqs,
     472                 :            :                         Xapian::termcount window);
     473                 :            : 
     474                 :            :     PostList * postlist();
     475                 :            : };
     476                 :            : 
     477                 :            : PostList *
     478                 :        688 : AndContext::PosFilter::postlist(PostList* pl,
     479                 :            :                                 const vector<PostList*>& pls,
     480                 :            :                                 PostListTree* pltree) const
     481                 :            : try {
     482                 :        688 :     vector<PostList *>::const_iterator terms_begin = pls.begin() + begin;
     483                 :        688 :     vector<PostList *>::const_iterator terms_end = pls.begin() + end;
     484                 :            : 
     485         [ +  + ]:        688 :     if (op_ == Xapian::Query::OP_NEAR) {
     486 [ +  - ][ +  - ]:        214 :         pl = new NearPostList(pl, window, terms_begin, terms_end, pltree);
     487         [ +  + ]:        474 :     } else if (window == end - begin) {
     488                 :            :         AssertEq(op_, Xapian::Query::OP_PHRASE);
     489 [ +  - ][ +  - ]:        326 :         pl = new ExactPhrasePostList(pl, terms_begin, terms_end, pltree);
     490                 :            :     } else {
     491                 :            :         AssertEq(op_, Xapian::Query::OP_PHRASE);
     492 [ +  - ][ +  - ]:        148 :         pl = new PhrasePostList(pl, window, terms_begin, terms_end, pltree);
     493                 :            :     }
     494                 :        688 :     return pl;
     495                 :          0 : } catch (...) {
     496         [ #  # ]:          0 :     delete pl;
     497                 :          0 :     throw;
     498                 :            : }
     499                 :            : 
     500                 :            : void
     501                 :        688 : AndContext::add_pos_filter(Query::op op_,
     502                 :            :                            size_t n_subqs,
     503                 :            :                            Xapian::termcount window)
     504                 :            : {
     505                 :            :     Assert(n_subqs > 1);
     506                 :        688 :     size_t end = pls.size();
     507                 :        688 :     size_t begin = end - n_subqs;
     508         [ +  - ]:        688 :     pos_filters.push_back(PosFilter(op_, begin, end, window));
     509                 :        688 : }
     510                 :            : 
     511                 :            : PostList *
     512                 :       1124 : AndContext::postlist()
     513                 :            : {
     514         [ +  + ]:       1124 :     if (pls.empty()) {
     515                 :            :         // This case only happens if this sub-database has no positional data
     516                 :            :         // (but another sub-database does).
     517                 :            :         Assert(pos_filters.empty());
     518         [ +  - ]:         16 :         return new EmptyPostList;
     519                 :            :     }
     520                 :            : 
     521                 :       2216 :     unique_ptr<PostList> pl(new MultiAndPostList(pls.begin(), pls.end(),
     522 [ +  - ][ +  - ]:       1108 :                                                  qopt->matcher, qopt->db_size));
     523                 :            : 
     524                 :            :     // Sort the positional filters to try to apply them in an efficient order.
     525                 :            :     // FIXME: We need to figure out what that is!  Try applying lowest cf/tf
     526                 :            :     // first?
     527                 :            : 
     528                 :            :     // Apply any positional filters.
     529                 :       1108 :     list<PosFilter>::const_iterator i;
     530         [ +  + ]:       1796 :     for (i = pos_filters.begin(); i != pos_filters.end(); ++i) {
     531                 :        688 :         const PosFilter & filter = *i;
     532         [ +  - ]:        688 :         pl.reset(filter.postlist(pl.release(), pls, qopt->matcher));
     533                 :            :     }
     534                 :            : 
     535                 :            :     // Empty pls so our destructor doesn't delete them all!
     536                 :       1108 :     pls.clear();
     537                 :       1124 :     return pl.release();
     538                 :            : }
     539                 :            : 
     540                 :            : }
     541                 :            : 
     542         [ -  + ]:    2666914 : Query::Internal::~Internal() { }
     543                 :            : 
     544                 :            : size_t
     545                 :        734 : Query::Internal::get_num_subqueries() const XAPIAN_NOEXCEPT
     546                 :            : {
     547                 :        734 :     return 0;
     548                 :            : }
     549                 :            : 
     550                 :            : const Query
     551                 :          0 : Query::Internal::get_subquery(size_t) const
     552                 :            : {
     553 [ #  # ][ #  # ]:          0 :     throw Xapian::InvalidArgumentError("get_subquery() not meaningful for this Query object");
                 [ #  # ]
     554                 :            : }
     555                 :            : 
     556                 :            : Xapian::termcount
     557                 :          0 : Query::Internal::get_wqf() const
     558                 :            : {
     559 [ #  # ][ #  # ]:          0 :     throw Xapian::InvalidArgumentError("get_wqf() not meaningful for this Query object");
                 [ #  # ]
     560                 :            : }
     561                 :            : 
     562                 :            : Xapian::termpos
     563                 :          0 : Query::Internal::get_pos() const
     564                 :            : {
     565 [ #  # ][ #  # ]:          0 :     throw Xapian::InvalidArgumentError("get_pos() not meaningful for this Query object");
                 [ #  # ]
     566                 :            : }
     567                 :            : 
     568                 :            : void
     569                 :      23861 : Query::Internal::gather_terms(void *) const
     570                 :            : {
     571                 :      23861 : }
     572                 :            : 
     573                 :            : Xapian::termcount
     574                 :      10906 : Query::Internal::get_length() const XAPIAN_NOEXCEPT
     575                 :            : {
     576                 :      10906 :     return 0;
     577                 :            : }
     578                 :            : 
     579                 :            : Query::Internal *
     580                 :      13457 : Query::Internal::unserialise(const char ** p, const char * end,
     581                 :            :                              const Registry & reg)
     582                 :            : {
     583         [ +  + ]:      13457 :     if (*p == end)
     584                 :          1 :         return NULL;
     585                 :      13456 :     unsigned char ch = *(*p)++;
     586   [ +  +  +  +  :      13456 :     switch (ch >> 5) {
                      - ]
     587                 :            :         case 4: case 5: case 6: case 7: {
     588                 :            :             // Multi-way branch
     589                 :            :             //
     590                 :            :             // 1ccccnnn where:
     591                 :            :             //   nnn -> n_subqs (0 means encoded value follows)
     592                 :            :             //   cccc -> code (which OP_XXX)
     593                 :        836 :             size_t n_subqs = ch & 0x07;
     594         [ +  + ]:        836 :             if (n_subqs == 0) {
     595         [ +  - ]:         46 :                 decode_length(p, end, n_subqs);
     596                 :         46 :                 n_subqs += 8;
     597                 :            :             }
     598                 :        836 :             unsigned char code = (ch >> 3) & 0x0f;
     599                 :        836 :             Xapian::termcount parameter = 0;
     600         [ +  + ]:        836 :             if (code >= 13)
     601         [ +  - ]:        231 :                 decode_length(p, end, parameter);
     602                 :            :             Xapian::Internal::QueryBranch * result;
     603   [ +  +  +  +  :        836 :             switch (code) {
          +  +  +  +  +  
                +  +  - ]
     604                 :            :                 case 0: // OP_AND
     605 [ +  - ][ +  - ]:         58 :                     result = new Xapian::Internal::QueryAnd(n_subqs);
     606                 :         58 :                     break;
     607                 :            :                 case 1: // OP_OR
     608 [ +  - ][ +  - ]:        371 :                     result = new Xapian::Internal::QueryOr(n_subqs);
     609                 :        371 :                     break;
     610                 :            :                 case 2: // OP_AND_NOT
     611 [ +  - ][ +  - ]:         32 :                     result = new Xapian::Internal::QueryAndNot(n_subqs);
     612                 :         32 :                     break;
     613                 :            :                 case 3: // OP_XOR
     614 [ +  - ][ +  - ]:         26 :                     result = new Xapian::Internal::QueryXor(n_subqs);
     615                 :         26 :                     break;
     616                 :            :                 case 4: // OP_AND_MAYBE
     617 [ +  - ][ +  - ]:         16 :                     result = new Xapian::Internal::QueryAndMaybe(n_subqs);
     618                 :         16 :                     break;
     619                 :            :                 case 5: // OP_FILTER
     620 [ +  - ][ +  - ]:         10 :                     result = new Xapian::Internal::QueryFilter(n_subqs);
     621                 :         10 :                     break;
     622                 :            :                 case 6: // OP_SYNONYM
     623 [ +  - ][ +  - ]:         90 :                     result = new Xapian::Internal::QuerySynonym(n_subqs);
     624                 :         90 :                     break;
     625                 :            :                 case 7: // OP_MAX
     626 [ +  - ][ +  - ]:          2 :                     result = new Xapian::Internal::QueryMax(n_subqs);
     627                 :          2 :                     break;
     628                 :            :                 case 13: // OP_ELITE_SET
     629                 :            :                     result = new Xapian::Internal::QueryEliteSet(n_subqs,
     630 [ +  - ][ +  - ]:         56 :                                                                  parameter);
     631                 :         56 :                     break;
     632                 :            :                 case 14: // OP_NEAR
     633                 :            :                     result = new Xapian::Internal::QueryNear(n_subqs,
     634 [ +  - ][ +  - ]:         58 :                                                              parameter);
     635                 :         58 :                     break;
     636                 :            :                 case 15: // OP_PHRASE
     637                 :            :                     result = new Xapian::Internal::QueryPhrase(n_subqs,
     638 [ +  - ][ +  - ]:        117 :                                                                parameter);
     639                 :        117 :                     break;
     640                 :            :                 default:
     641                 :            :                     // 8 to 12 are currently unused.
     642 [ #  # ][ #  # ]:          0 :                     throw SerialisationError("Unknown multi-way branch Query operator");
                 [ #  # ]
     643                 :            :             }
     644         [ +  + ]:       2919 :             do {
     645 [ +  - ][ +  - ]:       2919 :                 result->add_subquery(Xapian::Query(unserialise(p, end, reg)));
                 [ +  - ]
     646                 :            :             } while (--n_subqs);
     647         [ +  - ]:        836 :             result->done();
     648                 :        836 :             return result;
     649                 :            :         }
     650                 :            :         case 2: case 3: { // Term
     651                 :            :             // Term
     652                 :            :             //
     653                 :            :             // 01ccLLLL where:
     654                 :            :             //   LLLL -> length (0 means encoded value follows)
     655                 :            :             //   cc -> code:
     656                 :            :             //     0: wqf = 0; pos = 0
     657                 :            :             //     1: wqf = 1; pos = 0
     658                 :            :             //     2: wqf = 1; pos -> encoded value follows
     659                 :            :             //     3: wqf -> encoded value follows; pos -> encoded value follows
     660                 :       8344 :             size_t len = ch & 0x0f;
     661         [ -  + ]:       8344 :             if (len == 0) {
     662         [ #  # ]:          0 :                 decode_length(p, end, len);
     663                 :          0 :                 len += 16;
     664                 :            :             }
     665         [ -  + ]:       8344 :             if (size_t(end - *p) < len)
     666 [ #  # ][ #  # ]:          0 :                 throw SerialisationError("Not enough data");
                 [ #  # ]
     667         [ +  - ]:       8344 :             string term(*p, len);
     668                 :       8344 :             *p += len;
     669                 :            : 
     670                 :       8344 :             int code = ((ch >> 4) & 0x03);
     671                 :            : 
     672                 :       8344 :             Xapian::termcount wqf = static_cast<Xapian::termcount>(code > 0);
     673         [ +  + ]:       8344 :             if (code == 3)
     674         [ +  - ]:          6 :                 decode_length(p, end, wqf);
     675                 :            : 
     676                 :       8344 :             Xapian::termpos pos = 0;
     677         [ +  + ]:       8344 :             if (code >= 2)
     678         [ +  - ]:        449 :                 decode_length(p, end, pos);
     679                 :            : 
     680 [ +  - ][ +  - ]:       8344 :             return new Xapian::Internal::QueryTerm(term, wqf, pos);
     681                 :            :         }
     682                 :            :         case 1: {
     683                 :            :             // OP_VALUE_RANGE or OP_VALUE_GE or OP_VALUE_LE
     684                 :            :             //
     685                 :            :             // 001tssss where:
     686                 :            :             //   ssss -> slot number (15 means encoded value follows)
     687                 :            :             //   t -> op:
     688                 :            :             //     0: OP_VALUE_RANGE (or OP_VALUE_LE if begin empty)
     689                 :            :             //     1: OP_VALUE_GE
     690                 :       3996 :             Xapian::valueno slot = ch & 15;
     691         [ -  + ]:       3996 :             if (slot == 15) {
     692         [ #  # ]:          0 :                 decode_length(p, end, slot);
     693                 :          0 :                 slot += 15;
     694                 :            :             }
     695                 :            :             size_t len;
     696         [ +  - ]:       3996 :             decode_length_and_check(p, end, len);
     697         [ +  - ]:       3996 :             string begin(*p, len);
     698                 :       3996 :             *p += len;
     699         [ +  + ]:       3996 :             if (ch & 0x10) {
     700                 :            :                 // OP_VALUE_GE
     701 [ +  - ][ +  - ]:         26 :                 return new Xapian::Internal::QueryValueGE(slot, begin);
     702                 :            :             }
     703                 :            : 
     704                 :            :             // OP_VALUE_RANGE
     705         [ +  - ]:       3970 :             decode_length_and_check(p, end, len);
     706         [ +  - ]:       7940 :             string end_(*p, len);
     707                 :       3970 :             *p += len;
     708         [ +  + ]:       3970 :             if (begin.empty()) // FIXME: is this right?
     709 [ +  - ][ +  - ]:         50 :                 return new Xapian::Internal::QueryValueLE(slot, end_);
     710 [ +  - ][ +  - ]:       7916 :             return new Xapian::Internal::QueryValueRange(slot, begin, end_);
     711                 :            :         }
     712                 :            :         case 0: {
     713                 :            :             // Other operators
     714                 :            :             //
     715                 :            :             //   000ttttt where:
     716                 :            :             //     ttttt -> encodes which OP_XXX
     717   [ -  +  +  +  :        280 :             switch (ch & 0x1f) {
                -  +  - ]
     718                 :            :                 case 0x00: // OP_INVALID
     719         [ #  # ]:          0 :                     return new Xapian::Internal::QueryInvalid();
     720                 :            :                 case 0x0b: { // Wildcard
     721         [ -  + ]:         66 :                     if (*p == end)
     722 [ #  # ][ #  # ]:          0 :                         throw SerialisationError("not enough data");
                 [ #  # ]
     723                 :            :                     Xapian::termcount max_expansion;
     724         [ +  - ]:         66 :                     decode_length(p, end, max_expansion);
     725         [ -  + ]:         66 :                     if (end - *p < 2)
     726 [ #  # ][ #  # ]:          0 :                         throw SerialisationError("not enough data");
                 [ #  # ]
     727                 :         66 :                     int flags = static_cast<unsigned char>(*(*p)++);
     728                 :         66 :                     op combiner = static_cast<op>(*(*p)++);
     729                 :            :                     size_t len;
     730         [ +  - ]:         66 :                     decode_length_and_check(p, end, len);
     731         [ +  - ]:         66 :                     string pattern(*p, len);
     732                 :         66 :                     *p += len;
     733                 :            :                     return new Xapian::Internal::QueryWildcard(pattern,
     734                 :            :                                                                max_expansion,
     735                 :            :                                                                flags,
     736 [ +  - ][ +  - ]:         66 :                                                                combiner);
     737                 :            :                 }
     738                 :            :                 case 0x0c: { // PostingSource
     739                 :            :                     size_t len;
     740         [ +  - ]:         43 :                     decode_length_and_check(p, end, len);
     741         [ +  - ]:         43 :                     string name(*p, len);
     742                 :         43 :                     *p += len;
     743                 :            : 
     744         [ +  - ]:         43 :                     const PostingSource * reg_source = reg.get_posting_source(name);
     745         [ +  + ]:         43 :                     if (!reg_source) {
     746         [ +  - ]:          2 :                         string m = "PostingSource ";
     747         [ +  - ]:          2 :                         m += name;
     748         [ +  - ]:          2 :                         m += " not registered";
     749 [ +  - ][ +  - ]:          2 :                         throw SerialisationError(m);
     750                 :            :                     }
     751                 :            : 
     752         [ +  - ]:         41 :                     decode_length_and_check(p, end, len);
     753                 :            :                     PostingSource * source =
     754                 :            :                         reg_source->unserialise_with_registry(string(*p, len),
     755 [ +  - ][ +  - ]:         41 :                                                               reg);
     756                 :         41 :                     *p += len;
     757 [ +  - ][ +  - ]:         43 :                     return new Xapian::Internal::QueryPostingSource(source->release());
     758                 :            :                 }
     759                 :            :                 case 0x0d: {
     760                 :            :                     using Xapian::Internal::QueryScaleWeight;
     761         [ +  - ]:        161 :                     double scale_factor = unserialise_double(p, end);
     762                 :            :                     return new QueryScaleWeight(scale_factor,
     763 [ +  - ][ +  - ]:        161 :                                                 Query(unserialise(p, end, reg)));
         [ +  - ][ +  - ]
     764                 :            :                 }
     765                 :            :                 case 0x0e: {
     766                 :            :                     Xapian::termcount wqf;
     767                 :            :                     Xapian::termpos pos;
     768         [ #  # ]:          0 :                     decode_length(p, end, wqf);
     769         [ #  # ]:          0 :                     decode_length(p, end, pos);
     770 [ #  # ][ #  # ]:          0 :                     return new Xapian::Internal::QueryTerm(string(), wqf, pos);
                 [ #  # ]
     771                 :            :                 }
     772                 :            :                 case 0x0f:
     773 [ +  - ][ +  - ]:         10 :                     return new Xapian::Internal::QueryTerm();
     774                 :            :                 default: // Others currently unused.
     775                 :          0 :                     break;
     776                 :            :             }
     777                 :          0 :             break;
     778                 :            :         }
     779                 :            :     }
     780         [ #  # ]:          0 :     string msg = "Unknown Query serialisation: ";
     781 [ #  # ][ #  # ]:          0 :     msg += str(ch);
     782 [ #  # ][ #  # ]:      13455 :     throw SerialisationError(msg);
     783                 :            : }
     784                 :            : 
     785                 :            : void
     786                 :        992 : Query::Internal::postlist_sub_and_like(AndContext& ctx,
     787                 :            :                                        QueryOptimiser * qopt,
     788                 :            :                                        double factor) const
     789                 :            : {
     790                 :        992 :     ctx.add_postlist(postlist(qopt, factor));
     791                 :        992 : }
     792                 :            : 
     793                 :            : void
     794                 :     285396 : Query::Internal::postlist_sub_or_like(OrContext& ctx,
     795                 :            :                                       QueryOptimiser * qopt,
     796                 :            :                                       double factor) const
     797                 :            : {
     798                 :     285396 :     ctx.add_postlist(postlist(qopt, factor));
     799                 :     285396 : }
     800                 :            : 
     801                 :            : void
     802                 :       1332 : Query::Internal::postlist_sub_bool_or_like(BoolOrContext& ctx,
     803                 :            :                                            QueryOptimiser * qopt) const
     804                 :            : {
     805                 :       1332 :     ctx.add_postlist(postlist(qopt, 0.0));
     806                 :       1332 : }
     807                 :            : 
     808                 :            : void
     809                 :        312 : Query::Internal::postlist_sub_xor(XorContext& ctx,
     810                 :            :                                   QueryOptimiser * qopt,
     811                 :            :                                   double factor) const
     812                 :            : {
     813                 :        312 :     ctx.add_postlist(postlist(qopt, factor));
     814                 :        312 : }
     815                 :            : 
     816                 :            : namespace Internal {
     817                 :            : 
     818                 :            : Query::op
     819                 :      26933 : QueryTerm::get_type() const XAPIAN_NOEXCEPT
     820                 :            : {
     821         [ +  + ]:      26933 :     return term.empty() ? Query::LEAF_MATCH_ALL : Query::LEAF_TERM;
     822                 :            : }
     823                 :            : 
     824                 :            : string
     825                 :       6488 : QueryTerm::get_description() const
     826                 :            : {
     827                 :       6488 :     string desc;
     828         [ +  + ]:       6488 :     if (term.empty()) {
     829         [ +  - ]:         26 :         desc = "<alldocuments>";
     830                 :            :     } else {
     831         [ +  - ]:       6462 :         description_append(desc, term);
     832                 :            :     }
     833         [ -  + ]:       6488 :     if (wqf != 1) {
     834         [ #  # ]:          0 :         desc += '#';
     835 [ #  # ][ #  # ]:          0 :         desc += str(wqf);
     836                 :            :     }
     837         [ +  + ]:       6488 :     if (pos) {
     838         [ +  - ]:       4859 :         desc += '@';
     839 [ +  - ][ +  - ]:       4859 :         desc += str(pos);
     840                 :            :     }
     841                 :       6488 :     return desc;
     842                 :            : }
     843                 :            : 
     844                 :        667 : QueryPostingSource::QueryPostingSource(PostingSource * source_)
     845                 :        671 :     : source(source_)
     846                 :            : {
     847         [ +  + ]:        667 :     if (!source_)
     848 [ +  - ][ +  - ]:          4 :         throw Xapian::InvalidArgumentError("source parameter can't be NULL");
                 [ +  - ]
     849         [ +  + ]:        663 :     if (source->_refs == 0) {
     850                 :            :         // source_ isn't reference counted, so try to clone it.  If clone()
     851                 :            :         // isn't implemented, just use the object provided and it's the
     852                 :            :         // caller's responsibility to ensure it stays valid while in use.
     853         [ +  - ]:        621 :         PostingSource * cloned_source = source->clone();
     854 [ +  + ][ +  - ]:        621 :         if (cloned_source) source = cloned_source->release();
     855                 :            :     }
     856                 :        663 : }
     857                 :            : 
     858                 :            : Query::op
     859                 :         27 : QueryPostingSource::get_type() const XAPIAN_NOEXCEPT
     860                 :            : {
     861                 :         27 :     return Query::LEAF_POSTING_SOURCE;
     862                 :            : }
     863                 :            : 
     864                 :            : string
     865                 :         21 : QueryPostingSource::get_description() const
     866                 :            : {
     867         [ +  - ]:         21 :     string desc = "PostingSource(";
     868 [ +  - ][ +  - ]:         21 :     desc += source->get_description();
     869         [ +  - ]:         21 :     desc += ')';
     870                 :         21 :     return desc;
     871                 :            : }
     872                 :            : 
     873                 :       1023 : QueryScaleWeight::QueryScaleWeight(double factor, const Query & subquery_)
     874         [ +  - ]:       1247 :     : scale_factor(factor), subquery(subquery_)
     875                 :            : {
     876         [ +  + ]:       1023 :     if (rare(scale_factor < 0.0))
     877 [ +  - ][ +  - ]:        224 :         throw Xapian::InvalidArgumentError("OP_SCALE_WEIGHT requires factor >= 0");
                 [ +  - ]
     878                 :        799 : }
     879                 :            : 
     880                 :            : Query::op
     881                 :         11 : QueryScaleWeight::get_type() const XAPIAN_NOEXCEPT
     882                 :            : {
     883                 :         11 :     return Query::OP_SCALE_WEIGHT;
     884                 :            : }
     885                 :            : 
     886                 :            : size_t
     887                 :          2 : QueryScaleWeight::get_num_subqueries() const XAPIAN_NOEXCEPT
     888                 :            : {
     889                 :          2 :     return 1;
     890                 :            : }
     891                 :            : 
     892                 :            : const Query
     893                 :          5 : QueryScaleWeight::get_subquery(size_t) const
     894                 :            : {
     895                 :          5 :     return subquery;
     896                 :            : }
     897                 :            : 
     898                 :            : string
     899                 :        348 : QueryScaleWeight::get_description() const
     900                 :            : {
     901                 :            :     Assert(subquery.internal.get());
     902                 :        348 :     string desc = str(scale_factor);
     903         [ +  - ]:        348 :     desc += " * ";
     904 [ +  - ][ +  - ]:        348 :     desc += subquery.internal->get_description();
     905                 :        348 :     return desc;
     906                 :            : }
     907                 :            : 
     908                 :            : PostList*
     909                 :     312541 : QueryTerm::postlist(QueryOptimiser * qopt, double factor) const
     910                 :            : {
     911                 :            :     LOGCALL(QUERY, PostList*, "QueryTerm::postlist", qopt | factor);
     912         [ +  + ]:     312541 :     if (factor != 0.0)
     913                 :     310737 :         qopt->inc_total_subqs();
     914                 :     312541 :     RETURN(qopt->open_post_list(term, wqf, factor));
     915                 :            : }
     916                 :            : 
     917                 :            : PostList*
     918                 :        664 : QueryPostingSource::postlist(QueryOptimiser * qopt, double factor) const
     919                 :            : {
     920                 :            :     LOGCALL(QUERY, PostList*, "QueryPostingSource::postlist", qopt | factor);
     921                 :            :     Assert(source.get());
     922         [ +  + ]:        664 :     if (factor != 0.0)
     923                 :        640 :         qopt->inc_total_subqs();
     924                 :            :     // Casting away const on the Database::Internal here is OK, as we wrap
     925                 :            :     // them in a const Xapian::Database so non-const methods can't actually
     926                 :            :     // be called on the Database::Internal object.
     927                 :            :     const Xapian::Database wrappeddb(
     928         [ +  - ]:        664 :             const_cast<Xapian::Database::Internal*>(&(qopt->db)));
     929 [ +  - ][ +  - ]:        664 :     RETURN(new ExternalPostList(wrappeddb, source.get(), factor, qopt->matcher));
     930                 :            : }
     931                 :            : 
     932                 :            : PostList*
     933                 :        654 : QueryScaleWeight::postlist(QueryOptimiser * qopt, double factor) const
     934                 :            : {
     935                 :            :     LOGCALL(QUERY, PostList*, "QueryScaleWeight::postlist", qopt | factor);
     936                 :        654 :     RETURN(subquery.internal->postlist(qopt, factor * scale_factor));
     937                 :            : }
     938                 :            : 
     939                 :            : void
     940                 :     524176 : QueryTerm::gather_terms(void * void_terms) const
     941                 :            : {
     942                 :            :     // Skip Xapian::Query::MatchAll (aka Xapian::Query("")).
     943         [ +  + ]:     524176 :     if (!term.empty()) {
     944                 :            :         vector<pair<Xapian::termpos, string>> &terms =
     945                 :     524044 :             *static_cast<vector<pair<Xapian::termpos, string>>*>(void_terms);
     946         [ +  - ]:     524044 :         terms.push_back(make_pair(pos, term));
     947                 :            :     }
     948                 :     524176 : }
     949                 :            : 
     950                 :            : PostList*
     951                 :      11974 : QueryValueRange::postlist(QueryOptimiser *qopt, double factor) const
     952                 :            : {
     953                 :            :     LOGCALL(QUERY, PostList*, "QueryValueRange::postlist", qopt | factor);
     954         [ +  - ]:      11974 :     if (factor != 0.0)
     955                 :      11974 :         qopt->inc_total_subqs();
     956                 :      11974 :     const Xapian::Database::Internal & db = qopt->db;
     957                 :      11974 :     const string & lb = db.get_value_lower_bound(slot);
     958         [ +  + ]:      11974 :     if (lb.empty()) {
     959                 :            :         // This should only happen if there are no values in this slot (which
     960                 :            :         // could be because the backend just doesn't support values at all).
     961                 :            :         // If there were values in the slot, the backend should have a
     962                 :            :         // non-empty lower bound, even if it isn't a tight one.
     963                 :            :         AssertEq(db.get_value_freq(slot), 0);
     964         [ +  - ]:          4 :         RETURN(new EmptyPostList);
     965                 :            :     }
     966 [ +  - ][ +  + ]:      11970 :     if (end < lb) {
     967         [ +  - ]:         23 :         RETURN(new EmptyPostList);
     968                 :            :     }
     969         [ +  - ]:      23894 :     const string & ub = db.get_value_upper_bound(slot);
     970 [ +  - ][ +  + ]:      11947 :     if (begin > ub) {
     971         [ +  - ]:         25 :         RETURN(new EmptyPostList);
     972                 :            :     }
     973 [ +  - ][ +  + ]:      11922 :     if (end >= ub) {
     974                 :            :         // If begin <= lb too, then the range check isn't needed, but we do
     975                 :            :         // still need to consider which documents have a value set in this
     976                 :            :         // slot.  If this value is set for all documents, we can replace it
     977                 :            :         // with the MatchAll postlist, which is especially efficient if
     978                 :            :         // there are no gaps in the docids.
     979 [ +  - ][ +  + ]:        554 :         if (begin <= lb && db.get_value_freq(slot) == db.get_doccount()) {
         [ +  - ][ +  - ]
         [ +  + ][ +  + ]
     980 [ +  - ][ +  - ]:         25 :             RETURN(db.open_post_list(string()));
     981                 :            :         }
     982 [ +  - ][ +  - ]:        529 :         RETURN(new ValueGePostList(&db, slot, begin));
     983                 :            :     }
     984 [ +  - ][ +  - ]:      23342 :     RETURN(new ValueRangePostList(&db, slot, begin, end));
     985                 :            : }
     986                 :            : 
     987                 :            : void
     988                 :       3920 : QueryValueRange::serialise(string & result) const
     989                 :            : {
     990         [ +  - ]:       3920 :     if (slot < 15) {
     991                 :       3920 :         result += static_cast<char>(0x20 | slot);
     992                 :            :     } else {
     993                 :          0 :         result += static_cast<char>(0x20 | 15);
     994         [ #  # ]:          0 :         result += encode_length(slot - 15);
     995                 :            :     }
     996         [ +  - ]:       3920 :     result += encode_length(begin.size());
     997                 :       3920 :     result += begin;
     998         [ +  - ]:       3920 :     result += encode_length(end.size());
     999                 :       3920 :     result += end;
    1000                 :       3920 : }
    1001                 :            : 
    1002                 :            : Query::op
    1003                 :      19005 : QueryValueRange::get_type() const XAPIAN_NOEXCEPT
    1004                 :            : {
    1005                 :      19005 :     return Query::OP_VALUE_RANGE;
    1006                 :            : }
    1007                 :            : 
    1008                 :            : string
    1009                 :         55 : QueryValueRange::get_description() const
    1010                 :            : {
    1011         [ +  - ]:         55 :     string desc = "VALUE_RANGE ";
    1012 [ +  - ][ +  - ]:         55 :     desc += str(slot);
    1013         [ +  - ]:         55 :     desc += ' ';
    1014         [ +  - ]:         55 :     description_append(desc, begin);
    1015         [ +  - ]:         55 :     desc += ' ';
    1016         [ +  - ]:         55 :     description_append(desc, end);
    1017                 :         55 :     return desc;
    1018                 :            : }
    1019                 :            : 
    1020                 :            : PostList*
    1021                 :        198 : QueryValueLE::postlist(QueryOptimiser *qopt, double factor) const
    1022                 :            : {
    1023                 :            :     LOGCALL(QUERY, PostList*, "QueryValueLE::postlist", qopt | factor);
    1024         [ +  - ]:        198 :     if (factor != 0.0)
    1025                 :        198 :         qopt->inc_total_subqs();
    1026                 :        198 :     const Xapian::Database::Internal & db = qopt->db;
    1027                 :        198 :     const string & lb = db.get_value_lower_bound(slot);
    1028         [ +  + ]:        198 :     if (lb.empty()) {
    1029                 :            :         // This should only happen if there are no values in this slot (which
    1030                 :            :         // could be because the backend just doesn't support values at all).
    1031                 :            :         // If there were values in the slot, the backend should have a
    1032                 :            :         // non-empty lower bound, even if it isn't a tight one.
    1033                 :            :         AssertEq(db.get_value_freq(slot), 0);
    1034         [ +  - ]:          1 :         RETURN(new EmptyPostList);
    1035                 :            :     }
    1036 [ +  - ][ +  + ]:        197 :     if (limit < lb) {
    1037         [ +  - ]:         18 :         RETURN(new EmptyPostList);
    1038                 :            :     }
    1039 [ +  - ][ +  - ]:        179 :     if (limit >= db.get_value_upper_bound(slot)) {
                 [ +  + ]
    1040                 :            :         // The range check isn't needed, but we do still need to consider
    1041                 :            :         // which documents have a value set in this slot.  If this value is
    1042                 :            :         // set for all documents, we can replace it with the MatchAll
    1043                 :            :         // postlist, which is especially efficient if there are no gaps in
    1044                 :            :         // the docids.
    1045 [ +  - ][ +  - ]:         21 :         if (db.get_value_freq(slot) == db.get_doccount()) {
                 [ +  - ]
    1046 [ +  - ][ +  - ]:         21 :             RETURN(db.open_post_list(string()));
    1047                 :            :         }
    1048                 :            :     }
    1049 [ +  - ][ +  - ]:        198 :     RETURN(new ValueRangePostList(&db, slot, string(), limit));
                 [ +  - ]
    1050                 :            : }
    1051                 :            : 
    1052                 :            : void
    1053                 :         50 : QueryValueLE::serialise(string & result) const
    1054                 :            : {
    1055                 :            :     // Encode as a range with an empty start (which only takes a single byte to
    1056                 :            :     // encode).
    1057         [ +  - ]:         50 :     if (slot < 15) {
    1058                 :         50 :         result += static_cast<char>(0x20 | slot);
    1059                 :            :     } else {
    1060                 :          0 :         result += static_cast<char>(0x20 | 15);
    1061         [ #  # ]:          0 :         result += encode_length(slot - 15);
    1062                 :            :     }
    1063         [ +  - ]:         50 :     result += encode_length(0);
    1064         [ +  - ]:         50 :     result += encode_length(limit.size());
    1065                 :         50 :     result += limit;
    1066                 :         50 : }
    1067                 :            : 
    1068                 :            : Query::op
    1069                 :         10 : QueryValueLE::get_type() const XAPIAN_NOEXCEPT
    1070                 :            : {
    1071                 :         10 :     return Query::OP_VALUE_LE;
    1072                 :            : }
    1073                 :            : 
    1074                 :            : string
    1075                 :          5 : QueryValueLE::get_description() const
    1076                 :            : {
    1077         [ +  - ]:          5 :     string desc = "VALUE_LE ";
    1078 [ +  - ][ +  - ]:          5 :     desc += str(slot);
    1079         [ +  - ]:          5 :     desc += ' ';
    1080         [ +  - ]:          5 :     description_append(desc, limit);
    1081                 :          5 :     return desc;
    1082                 :            : }
    1083                 :            : 
    1084                 :            : PostList*
    1085                 :        104 : QueryValueGE::postlist(QueryOptimiser *qopt, double factor) const
    1086                 :            : {
    1087                 :            :     LOGCALL(QUERY, PostList*, "QueryValueGE::postlist", qopt | factor);
    1088         [ +  - ]:        104 :     if (factor != 0.0)
    1089                 :        104 :         qopt->inc_total_subqs();
    1090                 :        104 :     const Xapian::Database::Internal & db = qopt->db;
    1091                 :        104 :     const string & lb = db.get_value_lower_bound(slot);
    1092         [ -  + ]:        104 :     if (lb.empty()) {
    1093                 :            :         // This should only happen if there are no values in this slot (which
    1094                 :            :         // could be because the backend just doesn't support values at all).
    1095                 :            :         // If there were values in the slot, the backend should have a
    1096                 :            :         // non-empty lower bound, even if it isn't a tight one.
    1097                 :            :         AssertEq(db.get_value_freq(slot), 0);
    1098         [ #  # ]:          0 :         RETURN(new EmptyPostList);
    1099                 :            :     }
    1100 [ +  - ][ +  - ]:        104 :     if (limit > db.get_value_upper_bound(slot)) {
                 [ +  + ]
    1101         [ +  - ]:          8 :         RETURN(new EmptyPostList);
    1102                 :            :     }
    1103 [ +  - ][ +  + ]:         96 :     if (limit < lb) {
    1104                 :            :         // The range check isn't needed, but we do still need to consider
    1105                 :            :         // which documents have a value set in this slot.  If this value is
    1106                 :            :         // set for all documents, we can replace it with the MatchAll
    1107                 :            :         // postlist, which is especially efficient if there are no gaps in
    1108                 :            :         // the docids.
    1109 [ +  - ][ +  - ]:          1 :         if (db.get_value_freq(slot) == db.get_doccount()) {
                 [ +  - ]
    1110 [ +  - ][ +  - ]:          1 :             RETURN(db.open_post_list(string()));
    1111                 :            :         }
    1112                 :            :     }
    1113 [ +  - ][ +  - ]:        104 :     RETURN(new ValueGePostList(&db, slot, limit));
    1114                 :            : }
    1115                 :            : 
    1116                 :            : void
    1117                 :         26 : QueryValueGE::serialise(string & result) const
    1118                 :            : {
    1119         [ +  - ]:         26 :     if (slot < 15) {
    1120                 :         26 :         result += static_cast<char>(0x20 | 0x10 | slot);
    1121                 :            :     } else {
    1122                 :          0 :         result += static_cast<char>(0x20 | 0x10 | 15);
    1123         [ #  # ]:          0 :         result += encode_length(slot - 15);
    1124                 :            :     }
    1125         [ +  - ]:         26 :     result += encode_length(limit.size());
    1126                 :         26 :     result += limit;
    1127                 :         26 : }
    1128                 :            : 
    1129                 :            : Query::op
    1130                 :          9 : QueryValueGE::get_type() const XAPIAN_NOEXCEPT
    1131                 :            : {
    1132                 :          9 :     return Query::OP_VALUE_GE;
    1133                 :            : }
    1134                 :            : 
    1135                 :            : string
    1136                 :          5 : QueryValueGE::get_description() const
    1137                 :            : {
    1138         [ +  - ]:          5 :     string desc = "VALUE_GE ";
    1139 [ +  - ][ +  - ]:          5 :     desc += str(slot);
    1140         [ +  - ]:          5 :     desc += ' ';
    1141         [ +  - ]:          5 :     description_append(desc, limit);
    1142                 :          5 :     return desc;
    1143                 :            : }
    1144                 :            : 
    1145                 :       1324 : QueryWildcard::QueryWildcard(const std::string &pattern_,
    1146                 :            :                              Xapian::termcount max_expansion_,
    1147                 :            :                              int flags_,
    1148                 :            :                              Query::op combiner_)
    1149                 :            :     : pattern(pattern_),
    1150                 :            :       max_expansion(max_expansion_),
    1151                 :            :       flags(flags_),
    1152 [ +  - ][ +  - ]:       1324 :       combiner(combiner_)
                 [ +  - ]
    1153                 :            : {
    1154         [ +  + ]:       1324 :     if ((flags & ~Query::WILDCARD_LIMIT_MASK_) == 0) {
    1155                 :       1243 :         head = min_len = pattern.size();
    1156                 :       1243 :         max_len = numeric_limits<decltype(max_len)>::max();
    1157         [ +  - ]:       1243 :         prefix = pattern;
    1158                 :       1324 :         return;
    1159                 :            :     }
    1160                 :            : 
    1161                 :         81 :     size_t i = 0;
    1162         [ +  + ]:        184 :     while (i != pattern.size()) {
    1163                 :            :         // Check for characters with special meaning.
    1164         [ +  - ]:        177 :         switch (pattern[i]) {
              [ +  +  + ]
    1165                 :            :             case '*':
    1166         [ +  + ]:         55 :                 if (flags & Query::WILDCARD_PATTERN_MULTI)
    1167                 :         48 :                     goto found_special;
    1168                 :          7 :                 break;
    1169                 :            :             case '?':
    1170         [ +  + ]:         33 :                 if (flags & Query::WILDCARD_PATTERN_SINGLE)
    1171                 :         26 :                     goto found_special;
    1172                 :          7 :                 break;
    1173                 :            :         }
    1174 [ +  - ][ +  - ]:        103 :         prefix += pattern[i];
    1175                 :        103 :         ++i;
    1176                 :        103 :         head = i;
    1177                 :            :     }
    1178                 :            : found_special:
    1179                 :            : 
    1180                 :         81 :     min_len = max_len = prefix.size();
    1181                 :            : 
    1182                 :         81 :     tail = i;
    1183                 :         81 :     bool had_qm = false, had_star = false;
    1184         [ +  + ]:        308 :     while (i != pattern.size()) {
    1185         [ +  - ]:        227 :         switch (pattern[i]) {
              [ +  +  + ]
    1186                 :            :             default:
    1187                 :            : default_case:
    1188 [ +  - ][ +  - ]:        127 :                 suffix += pattern[i];
    1189                 :        127 :                 ++min_len;
    1190                 :        127 :                 ++max_len;
    1191                 :        127 :                 break;
    1192                 :            : 
    1193                 :            :             case '*':
    1194         [ -  + ]:         59 :                 if (!(flags & Query::WILDCARD_PATTERN_MULTI))
    1195                 :          0 :                     goto default_case;
    1196                 :            :                 // Matches zero or more characters.
    1197                 :         59 :                 had_star = true;
    1198                 :         59 :                 tail = i + 1;
    1199         [ +  + ]:         59 :                 if (!suffix.empty()) {
    1200                 :          9 :                     check_pattern = true;
    1201         [ +  - ]:          9 :                     suffix.clear();
    1202                 :            :                 }
    1203                 :         59 :                 break;
    1204                 :            : 
    1205                 :            :             case '?':
    1206         [ -  + ]:         41 :                 if (!(flags & Query::WILDCARD_PATTERN_SINGLE))
    1207                 :          0 :                     goto default_case;
    1208                 :            :                 // Matches exactly one character.
    1209                 :         41 :                 tail = i + 1;
    1210         [ +  + ]:         41 :                 if (!suffix.empty()) {
    1211                 :          2 :                     check_pattern = true;
    1212         [ +  - ]:          2 :                     suffix.clear();
    1213                 :            :                 }
    1214                 :            :                 // `?` matches one Unicode character, which is 1-4 bytes in
    1215                 :            :                 // UTF-8, so we have to actually check the pattern if there's
    1216                 :            :                 // more than one `?` in it.
    1217         [ +  + ]:         41 :                 if (had_qm) {
    1218                 :         13 :                     check_pattern = true;
    1219                 :            :                 }
    1220                 :         41 :                 had_qm = true;
    1221                 :         41 :                 ++min_len;
    1222                 :         41 :                 max_len += MAX_UTF_8_CHARACTER_LENGTH;
    1223                 :         41 :                 break;
    1224                 :            :         }
    1225                 :            : 
    1226                 :        227 :         ++i;
    1227                 :            :     }
    1228                 :            : 
    1229         [ +  + ]:         81 :     if (had_star) {
    1230                 :         52 :         max_len = numeric_limits<decltype(max_len)>::max();
    1231                 :            :     } else {
    1232                 :            :         // If the pattern only contains `?` wildcards we'll need to check it
    1233                 :            :         // since `?` matches one Unicode character, which is 1-4 bytes in
    1234                 :            :         // UTF-8.  FIXME: We can avoid this if there's only one `?` wildcard
    1235                 :            :         // and the candidate is min_len bytes long.
    1236                 :         29 :         check_pattern = true;
    1237                 :            :     }
    1238                 :            : }
    1239                 :            : 
    1240                 :            : bool
    1241                 :        215 : QueryWildcard::test_wildcard_(const string& candidate, size_t o, size_t p,
    1242                 :            :                               size_t i) const
    1243                 :            : {
    1244                 :            :     // FIXME: Optimisation potential here.  We could compile the pattern to a
    1245                 :            :     // regex, or other tricks like calculating the min length needed after each
    1246                 :            :     // position that we test with this method - e.g. for foo*bar*x*baz there
    1247                 :            :     // must be at least 7 bytes after a position or there's no point testing if
    1248                 :            :     // "bar" matches there.
    1249         [ +  + ]:        370 :     for ( ; i != tail; ++i) {
    1250 [ +  + ][ +  + ]:        335 :         if ((flags & Query::WILDCARD_PATTERN_MULTI) && pattern[i] == '*') {
                 [ +  + ]
    1251         [ +  + ]:         35 :            if (++i == tail) {
    1252                 :            :                // '*' at end of variable part is easy!
    1253                 :         10 :                return true;
    1254                 :            :            }
    1255         [ +  + ]:        155 :            for (size_t test_o = o; test_o <= p; ++test_o) {
    1256         [ +  + ]:        140 :                if (test_wildcard_(candidate, test_o, p, i))
    1257                 :         10 :                    return true;
    1258                 :            :            }
    1259                 :         15 :            return false;
    1260                 :            :         }
    1261         [ +  + ]:        300 :         if (o == p) return false;
    1262 [ +  + ][ +  - ]:        270 :         if ((flags & Query::WILDCARD_PATTERN_SINGLE) && pattern[i] == '?') {
                 [ +  + ]
    1263                 :         50 :             unsigned char b = candidate[o];
    1264         [ +  + ]:         50 :             if (b < 0xc0) {
    1265                 :         20 :                 ++o;
    1266                 :         20 :                 continue;
    1267                 :            :             }
    1268                 :            :             unsigned seqlen;
    1269         [ +  + ]:         30 :             if (b < 0xe0) {
    1270                 :         10 :                 seqlen = 2;
    1271         [ +  + ]:         20 :             } else if (b < 0xf0) {
    1272                 :         10 :                 seqlen = 3;
    1273                 :            :             } else {
    1274                 :         10 :                 seqlen = 4;
    1275                 :            :             }
    1276         [ -  + ]:         30 :             if (rare(p - o < seqlen)) return false;
    1277                 :         30 :             o += seqlen;
    1278                 :         30 :             continue;
    1279                 :            :         }
    1280                 :            : 
    1281         [ +  + ]:        220 :         if (pattern[i] != candidate[o]) return false;
    1282                 :        105 :         ++o;
    1283                 :            :     }
    1284                 :         35 :     return (o == p);
    1285                 :            : }
    1286                 :            : 
    1287                 :            : bool
    1288                 :        910 : QueryWildcard::test_prefix_known(const string& candidate) const
    1289                 :            : {
    1290         [ +  + ]:        910 :     if (candidate.size() < min_len) return false;
    1291         [ -  + ]:        895 :     if (candidate.size() > max_len) return false;
    1292         [ +  + ]:        895 :     if (!endswith(candidate, suffix)) return false;
    1293                 :            : 
    1294         [ +  + ]:        880 :     if (!check_pattern) return true;
    1295                 :            : 
    1296                 :            :     return test_wildcard_(candidate, prefix.size(),
    1297                 :         75 :                           candidate.size() - suffix.size(),
    1298                 :         75 :                           head);
    1299                 :            : }
    1300                 :            : 
    1301                 :            : PostList*
    1302                 :        223 : QueryWildcard::postlist(QueryOptimiser * qopt, double factor) const
    1303                 :            : {
    1304                 :            :     LOGCALL(QUERY, PostList*, "QueryWildcard::postlist", qopt | factor);
    1305                 :        223 :     Query::op op = combiner;
    1306 [ +  + ][ +  - ]:        223 :     if (factor == 0.0 || op == Query::OP_SYNONYM) {
    1307         [ +  + ]:        223 :         if (factor == 0.0) {
    1308                 :            :             // If we have a factor of 0, we don't care about the weights, so
    1309                 :            :             // we're just like a normal OR query.
    1310                 :         16 :             op = Query::OP_OR;
    1311                 :            :         }
    1312                 :            : 
    1313                 :        223 :         bool old_in_synonym = qopt->in_synonym;
    1314         [ +  + ]:        223 :         if (!old_in_synonym) {
    1315                 :        207 :             qopt->in_synonym = (op == Query::OP_SYNONYM);
    1316                 :            :         }
    1317                 :            : 
    1318         [ +  - ]:        223 :         BoolOrContext ctx(qopt, 0);
    1319         [ +  + ]:        223 :         ctx.expand_wildcard(this, 0.0);
    1320                 :            : 
    1321         [ +  + ]:        192 :         if (op == Query::OP_SYNONYM) {
    1322                 :        176 :             qopt->inc_total_subqs();
    1323                 :            :         }
    1324                 :            : 
    1325                 :        192 :         qopt->in_synonym = old_in_synonym;
    1326                 :            : 
    1327         [ +  + ]:        192 :         if (ctx.empty())
    1328         [ +  - ]:         54 :             RETURN(new EmptyPostList);
    1329                 :            : 
    1330         [ +  - ]:        138 :         PostList * pl = ctx.postlist();
    1331         [ +  + ]:        138 :         if (op != Query::OP_SYNONYM)
    1332                 :          7 :             RETURN(pl);
    1333                 :            : 
    1334                 :            :         // We build an OP_OR tree for OP_SYNONYM and then wrap it in a
    1335                 :            :         // SynonymPostList, which supplies the weights.
    1336                 :            :         //
    1337                 :            :         // We know the subqueries from a wildcard expansion are wdf-disjoint
    1338                 :            :         // (i.e. each wdf from the document contributes at most itself to the
    1339                 :            :         // wdf of the subquery).
    1340         [ +  - ]:        223 :         RETURN(qopt->make_synonym_postlist(pl, factor, true));
    1341                 :            :     }
    1342                 :            : 
    1343         [ #  # ]:          0 :     OrContext ctx(qopt, 0);
    1344         [ #  # ]:          0 :     ctx.expand_wildcard(this, factor);
    1345                 :            : 
    1346                 :          0 :     qopt->set_total_subqs(qopt->get_total_subqs() + ctx.size());
    1347                 :            : 
    1348         [ #  # ]:          0 :     if (ctx.empty())
    1349         [ #  # ]:          0 :         RETURN(new EmptyPostList);
    1350                 :            : 
    1351         [ #  # ]:          0 :     if (op == Query::OP_MAX)
    1352         [ #  # ]:          0 :         RETURN(ctx.postlist_max());
    1353                 :            : 
    1354         [ #  # ]:        192 :     RETURN(ctx.postlist());
    1355                 :            : }
    1356                 :            : 
    1357                 :            : termcount
    1358                 :        201 : QueryWildcard::get_length() const XAPIAN_NOEXCEPT
    1359                 :            : {
    1360                 :            :     // We currently assume wqf is 1 for calculating the synonym's weight
    1361                 :            :     // since conceptually the synonym is one "virtual" term.  If we were
    1362                 :            :     // to combine multiple occurrences of the same synonym expansion into
    1363                 :            :     // a single instance with wqf set, we would want to track the wqf.
    1364                 :        201 :     return 1;
    1365                 :            : }
    1366                 :            : 
    1367                 :            : void
    1368                 :         66 : QueryWildcard::serialise(string & result) const
    1369                 :            : {
    1370                 :         66 :     result += static_cast<char>(0x0b);
    1371         [ +  - ]:         66 :     result += encode_length(max_expansion);
    1372                 :         66 :     result += static_cast<unsigned char>(flags);
    1373                 :         66 :     result += static_cast<unsigned char>(combiner);
    1374         [ +  - ]:         66 :     result += encode_length(pattern.size());
    1375                 :         66 :     result += pattern;
    1376                 :         66 : }
    1377                 :            : 
    1378                 :            : Query::op
    1379                 :        679 : QueryWildcard::get_type() const XAPIAN_NOEXCEPT
    1380                 :            : {
    1381                 :        679 :     return Query::OP_WILDCARD;
    1382                 :            : }
    1383                 :            : 
    1384                 :            : string
    1385                 :        547 : QueryWildcard::get_description() const
    1386                 :            : {
    1387         [ +  - ]:        547 :     string desc = "WILDCARD ";
    1388   [ +  -  +  - ]:        547 :     switch (combiner) {
    1389                 :            :         case Query::OP_SYNONYM:
    1390         [ +  - ]:        507 :             desc += "SYNONYM ";
    1391                 :        507 :             break;
    1392                 :            :         case Query::OP_MAX:
    1393         [ #  # ]:          0 :             desc += "MAX ";
    1394                 :          0 :             break;
    1395                 :            :         case Query::OP_OR:
    1396         [ +  - ]:         40 :             desc += "OR ";
    1397                 :         40 :             break;
    1398                 :            :         default:
    1399         [ #  # ]:          0 :             desc += "BAD ";
    1400                 :          0 :             break;
    1401                 :            :     }
    1402         [ +  - ]:        547 :     description_append(desc, pattern);
    1403                 :        547 :     return desc;
    1404                 :            : }
    1405                 :            : 
    1406                 :            : Xapian::termcount
    1407                 :       3365 : QueryBranch::get_length() const XAPIAN_NOEXCEPT
    1408                 :            : {
    1409                 :            :     // Sum results from all subqueries.
    1410                 :       3365 :     Xapian::termcount result = 0;
    1411                 :       3365 :     QueryVector::const_iterator i;
    1412         [ +  + ]:      14454 :     for (i = subqueries.begin(); i != subqueries.end(); ++i) {
    1413                 :            :         // MatchNothing subqueries should have been removed by done(), but we
    1414                 :            :         // can't use Assert in a XAPIAN_NOEXCEPT function.  But we'll get a
    1415                 :            :         // segfault anyway.
    1416                 :      11089 :         result += (*i).internal->get_length();
    1417                 :            :     }
    1418                 :       3365 :     return result;
    1419                 :            : }
    1420                 :            : 
    1421                 :            : #define MULTIWAY(X) static_cast<unsigned char>(0x80 | (X) << 3)
    1422                 :            : #define MISC(X) static_cast<unsigned char>(X)
    1423                 :            : void
    1424                 :        838 : QueryBranch::serialise_(string & result, Xapian::termcount parameter) const
    1425                 :            : {
    1426                 :            :     static const unsigned char first_byte[] = {
    1427                 :            :         MULTIWAY(0),    // OP_AND
    1428                 :            :         MULTIWAY(1),    // OP_OR
    1429                 :            :         MULTIWAY(2),    // OP_AND_NOT
    1430                 :            :         MULTIWAY(3),    // OP_XOR
    1431                 :            :         MULTIWAY(4),    // OP_AND_MAYBE
    1432                 :            :         MULTIWAY(5),    // OP_FILTER
    1433                 :            :         MULTIWAY(14),   // OP_NEAR
    1434                 :            :         MULTIWAY(15),   // OP_PHRASE
    1435                 :            :         0,              // OP_VALUE_RANGE
    1436                 :            :         MISC(3),        // OP_SCALE_WEIGHT
    1437                 :            :         MULTIWAY(13),   // OP_ELITE_SET
    1438                 :            :         0,              // OP_VALUE_GE
    1439                 :            :         0,              // OP_VALUE_LE
    1440                 :            :         MULTIWAY(6),    // OP_SYNONYM
    1441                 :            :         MULTIWAY(7)     // OP_MAX
    1442                 :            :     };
    1443         [ +  - ]:        838 :     Xapian::Query::op op_ = get_op();
    1444                 :            :     AssertRel(size_t(op_),<,sizeof(first_byte));
    1445                 :        838 :     unsigned char ch = first_byte[op_];
    1446         [ +  - ]:        838 :     if (ch & 0x80) {
    1447                 :            :         // Multi-way operator.
    1448 [ +  - ][ +  + ]:        838 :         if (subqueries.size() < 8)
    1449         [ +  - ]:        792 :             ch |= subqueries.size();
    1450         [ +  - ]:        838 :         result += ch;
    1451 [ +  - ][ +  + ]:        838 :         if (subqueries.size() >= 8)
    1452 [ +  - ][ +  - ]:         46 :             result += encode_length(subqueries.size() - 8);
                 [ +  - ]
    1453         [ +  + ]:        838 :         if (ch >= MULTIWAY(13))
    1454 [ +  - ][ +  - ]:        231 :             result += encode_length(parameter);
    1455                 :            :     } else {
    1456         [ #  # ]:          0 :         result += ch;
    1457                 :            :     }
    1458                 :            : 
    1459                 :        838 :     QueryVector::const_iterator i;
    1460 [ +  - ][ +  - ]:       3759 :     for (i = subqueries.begin(); i != subqueries.end(); ++i) {
         [ +  - ][ +  + ]
    1461                 :            :         // MatchNothing subqueries should have been removed by done().
    1462                 :            :         Assert((*i).internal.get());
    1463 [ +  - ][ +  + ]:       2923 :         (*i).internal->serialise(result);
    1464                 :            :     }
    1465                 :            : 
    1466                 :            :     // For OP_NEAR, OP_PHRASE, and OP_ELITE_SET, the window/set size gets
    1467                 :            :     // appended next by an overloaded serialise() method in the subclass.
    1468                 :        836 : }
    1469                 :            : 
    1470                 :            : void
    1471                 :        607 : QueryBranch::serialise(string & result) const
    1472                 :            : {
    1473                 :        607 :     QueryBranch::serialise_(result);
    1474                 :        605 : }
    1475                 :            : 
    1476                 :            : void
    1477                 :         58 : QueryNear::serialise(string & result) const
    1478                 :            : {
    1479                 :            :     // FIXME: window - subqueries.size() ?
    1480                 :         58 :     QueryBranch::serialise_(result, window);
    1481                 :         58 : }
    1482                 :            : 
    1483                 :            : void
    1484                 :        117 : QueryPhrase::serialise(string & result) const
    1485                 :            : {
    1486                 :            :     // FIXME: window - subqueries.size() ?
    1487                 :        117 :     QueryBranch::serialise_(result, window);
    1488                 :        117 : }
    1489                 :            : 
    1490                 :            : void
    1491                 :         56 : QueryEliteSet::serialise(string & result) const
    1492                 :            : {
    1493                 :            :     // FIXME: set_size - subqueries.size() ?
    1494                 :         56 :     QueryBranch::serialise_(result, set_size);
    1495                 :         56 : }
    1496                 :            : 
    1497                 :            : void
    1498                 :     237935 : QueryBranch::gather_terms(void * void_terms) const
    1499                 :            : {
    1500                 :            :     // Gather results from all subqueries.
    1501                 :     237935 :     QueryVector::const_iterator i;
    1502 [ +  - ][ +  - ]:     722932 :     for (i = subqueries.begin(); i != subqueries.end(); ++i) {
         [ +  - ][ +  + ]
    1503                 :            :         // MatchNothing subqueries should have been removed by done().
    1504                 :            :         Assert((*i).internal.get());
    1505 [ +  - ][ +  - ]:     484997 :         (*i).internal->gather_terms(void_terms);
    1506                 :            :     }
    1507                 :     237935 : }
    1508                 :            : 
    1509                 :            : void
    1510                 :        650 : QueryBranch::do_bool_or_like(BoolOrContext& ctx, QueryOptimiser* qopt) const
    1511                 :            : {
    1512                 :            :     LOGCALL_VOID(MATCH, "QueryBranch::do_bool_or_like", ctx | qopt);
    1513                 :            : 
    1514                 :            :     // FIXME: we could optimise by merging OP_ELITE_SET and OP_OR like we do
    1515                 :            :     // for AND-like operations.
    1516                 :            : 
    1517                 :            :     // OP_SYNONYM with a single subquery is only simplified by
    1518                 :            :     // QuerySynonym::done() if the single subquery is a term or MatchAll.
    1519                 :            :     Assert(subqueries.size() >= 2 || get_op() == Query::OP_SYNONYM);
    1520                 :            : 
    1521                 :        650 :     vector<PostList *> postlists;
    1522 [ +  - ][ +  - ]:        650 :     postlists.reserve(subqueries.size());
    1523                 :            : 
    1524 [ +  - ][ +  - ]:       1982 :     for (auto q : subqueries) {
         [ +  - ][ +  - ]
                 [ +  + ]
    1525                 :            :         // MatchNothing subqueries should have been removed by done().
    1526                 :            :         Assert(q.internal.get());
    1527         [ +  - ]:       1332 :         q.internal->postlist_sub_bool_or_like(ctx, qopt);
    1528                 :       1982 :     }
    1529                 :        650 : }
    1530                 :            : 
    1531                 :            : void
    1532                 :     140583 : QueryBranch::do_or_like(OrContext& ctx, QueryOptimiser * qopt, double factor,
    1533                 :            :                         Xapian::termcount elite_set_size, size_t first) const
    1534                 :            : {
    1535                 :            :     LOGCALL_VOID(MATCH, "QueryBranch::do_or_like", ctx | qopt | factor | elite_set_size);
    1536                 :            : 
    1537                 :            :     // FIXME: we could optimise by merging OP_ELITE_SET and OP_OR like we do
    1538                 :            :     // for AND-like operations.
    1539                 :            : 
    1540                 :            :     // OP_SYNONYM with a single subquery is only simplified by
    1541                 :            :     // QuerySynonym::done() if the single subquery is a term or MatchAll.
    1542                 :            :     Assert(subqueries.size() >= 2 || get_op() == Query::OP_SYNONYM);
    1543                 :            : 
    1544                 :     140583 :     vector<PostList *> postlists;
    1545 [ +  - ][ +  - ]:     140583 :     postlists.reserve(subqueries.size() - first);
    1546                 :            : 
    1547                 :     140583 :     QueryVector::const_iterator q;
    1548 [ +  - ][ +  - ]:     426121 :     for (q = subqueries.begin() + first; q != subqueries.end(); ++q) {
         [ +  - ][ +  - ]
                 [ +  + ]
    1549                 :            :         // MatchNothing subqueries should have been removed by done().
    1550                 :            :         Assert((*q).internal.get());
    1551 [ +  - ][ +  - ]:     285538 :         (*q).internal->postlist_sub_or_like(ctx, qopt, factor);
    1552                 :            :     }
    1553                 :            : 
    1554 [ +  + ][ +  - ]:     140583 :     if (elite_set_size && elite_set_size < subqueries.size()) {
         [ +  + ][ +  + ]
    1555 [ +  - ][ +  - ]:        208 :         ctx.select_elite_set(elite_set_size, subqueries.size());
    1556                 :            :         // FIXME: not right!
    1557                 :     140583 :     }
    1558                 :     140583 : }
    1559                 :            : 
    1560                 :            : PostList *
    1561                 :        650 : QueryBranch::do_synonym(QueryOptimiser * qopt, double factor) const
    1562                 :            : {
    1563                 :            :     LOGCALL(MATCH, PostList *, "QueryBranch::do_synonym", qopt | factor);
    1564 [ +  - ][ +  - ]:        650 :     BoolOrContext ctx(qopt, subqueries.size());
    1565         [ +  + ]:        650 :     if (factor == 0.0) {
    1566                 :            :         // If we have a factor of 0, we don't care about the weights, so
    1567                 :            :         // we're just like a normal OR query.
    1568         [ +  - ]:         48 :         do_bool_or_like(ctx, qopt);
    1569         [ +  - ]:         48 :         return ctx.postlist();
    1570                 :            :     }
    1571                 :            : 
    1572                 :        602 :     bool old_in_synonym = qopt->in_synonym;
    1573                 :            :     Assert(!old_in_synonym);
    1574                 :        602 :     qopt->in_synonym = true;
    1575         [ +  - ]:        602 :     do_bool_or_like(ctx, qopt);
    1576         [ +  - ]:        602 :     PostList * pl = ctx.postlist();
    1577                 :        602 :     qopt->in_synonym = old_in_synonym;
    1578                 :            : 
    1579                 :        602 :     bool wdf_disjoint = false;
    1580                 :            :     Assert(!subqueries.empty());
    1581         [ +  - ]:        602 :     auto type = subqueries.front().get_type();
    1582         [ +  + ]:        602 :     if (type == Query::OP_WILDCARD) {
    1583                 :            :         // Detect common easy case where all subqueries are OP_WILDCARD whose
    1584                 :            :         // constant prefixes form a prefix-free set.
    1585                 :          8 :         wdf_disjoint = true;
    1586                 :          8 :         vector<string> prefixes;
    1587 [ +  - ][ +  - ]:         24 :         for (auto&& q : subqueries) {
         [ +  - ][ +  - ]
                 [ +  + ]
    1588         [ -  + ]:         16 :             if (q.get_type() != Query::OP_WILDCARD) {
    1589                 :          0 :                 wdf_disjoint = false;
    1590                 :          0 :                 break;
    1591                 :            :             }
    1592                 :         16 :             auto qw = static_cast<const QueryWildcard*>(q.internal.get());
    1593 [ +  - ][ +  - ]:         16 :             prefixes.push_back(qw->get_fixed_prefix());
                 [ +  - ]
    1594                 :         16 :         }
    1595                 :            : 
    1596         [ +  - ]:          8 :         if (wdf_disjoint) {
    1597         [ +  - ]:          8 :             sort(prefixes.begin(), prefixes.end());
    1598                 :          8 :             const string* prev = nullptr;
    1599         [ +  + ]:         24 :             for (const auto& i : prefixes) {
    1600         [ +  + ]:         16 :                 if (prev) {
    1601         [ -  + ]:          8 :                     if (startswith(i, *prev)) {
    1602                 :          0 :                         wdf_disjoint = false;
    1603                 :          0 :                         break;
    1604                 :            :                     }
    1605                 :            :                 }
    1606                 :         16 :                 prev = &i;
    1607                 :            :             }
    1608                 :          8 :         }
    1609         [ +  + ]:        594 :     } else if (type == Query::LEAF_TERM) {
    1610                 :            :         // Detect common easy case where all subqueries are terms, none of
    1611                 :            :         // which are the same.
    1612                 :        562 :         wdf_disjoint = true;
    1613         [ +  - ]:        562 :         unordered_set<string> terms;
    1614 [ +  - ][ +  - ]:       1574 :         for (auto&& q : subqueries) {
         [ +  - ][ +  - ]
                 [ +  + ]
    1615         [ +  + ]:       1156 :             if (q.get_type() != Query::LEAF_TERM) {
    1616                 :        144 :                 wdf_disjoint = false;
    1617                 :        144 :                 break;
    1618                 :            :             }
    1619                 :       1012 :             auto qt = static_cast<const QueryTerm*>(q.internal.get());
    1620 [ +  - ][ -  + ]:       1012 :             if (!terms.insert(qt->get_term()).second) {
    1621                 :          0 :                 wdf_disjoint = false;
    1622         [ +  + ]:       1156 :                 break;
    1623                 :            :             }
    1624                 :       1574 :         }
    1625                 :            :     }
    1626                 :            : 
    1627                 :            :     // We currently assume wqf is 1 for calculating the synonym's weight
    1628                 :            :     // since conceptually the synonym is one "virtual" term.  If we were
    1629                 :            :     // to combine multiple occurrences of the same synonym expansion into
    1630                 :            :     // a single instance with wqf set, we would want to track the wqf.
    1631                 :            : 
    1632                 :            :     // We build an OP_OR tree for OP_SYNONYM and then wrap it in a
    1633                 :            :     // SynonymPostList, which supplies the weights.
    1634         [ +  - ]:        650 :     RETURN(qopt->make_synonym_postlist(pl, factor, wdf_disjoint));
    1635                 :            : }
    1636                 :            : 
    1637                 :            : PostList *
    1638                 :          8 : QueryBranch::do_max(QueryOptimiser * qopt, double factor) const
    1639                 :            : {
    1640                 :            :     LOGCALL(MATCH, PostList *, "QueryBranch::do_max", qopt | factor);
    1641 [ +  - ][ +  - ]:          8 :     OrContext ctx(qopt, subqueries.size());
    1642         [ +  - ]:          8 :     do_or_like(ctx, qopt, factor);
    1643         [ -  + ]:          8 :     if (factor == 0.0) {
    1644                 :            :         // If we have a factor of 0, we don't care about the weights, so
    1645                 :            :         // we're just like a normal OR query.
    1646         [ #  # ]:          0 :         RETURN(ctx.postlist());
    1647                 :            :     }
    1648                 :            : 
    1649                 :            :     // We currently assume wqf is 1 for calculating the OP_MAX's weight
    1650                 :            :     // since conceptually the OP_MAX is one "virtual" term.  If we were
    1651                 :            :     // to combine multiple occurrences of the same OP_MAX expansion into
    1652                 :            :     // a single instance with wqf set, we would want to track the wqf.
    1653         [ +  - ]:          8 :     RETURN(ctx.postlist_max());
    1654                 :            : }
    1655                 :            : 
    1656                 :            : Xapian::Query::op
    1657                 :       2082 : QueryBranch::get_type() const XAPIAN_NOEXCEPT
    1658                 :            : {
    1659                 :       2082 :     return get_op();
    1660                 :            : }
    1661                 :            : 
    1662                 :            : size_t
    1663                 :        343 : QueryBranch::get_num_subqueries() const XAPIAN_NOEXCEPT
    1664                 :            : {
    1665                 :        343 :     return subqueries.size();
    1666                 :            : }
    1667                 :            : 
    1668                 :            : const Query
    1669                 :        806 : QueryBranch::get_subquery(size_t n) const
    1670                 :            : {
    1671                 :        806 :     return subqueries[n];
    1672                 :            : }
    1673                 :            : 
    1674                 :            : const string
    1675                 :       2931 : QueryBranch::get_description_helper(const char * op,
    1676                 :            :                                     Xapian::termcount parameter) const
    1677                 :            : {
    1678         [ +  - ]:       2931 :     string desc = "(";
    1679                 :       2931 :     QueryVector::const_iterator i;
    1680 [ +  - ][ +  - ]:      10623 :     for (i = subqueries.begin(); i != subqueries.end(); ++i) {
         [ +  - ][ +  + ]
    1681         [ +  + ]:       7692 :         if (desc.size() > 1) {
    1682         [ +  - ]:       4761 :             desc += op;
    1683         [ +  + ]:       4761 :             if (parameter) {
    1684 [ +  - ][ +  - ]:        843 :                 desc += str(parameter);
    1685         [ +  - ]:        843 :                 desc += ' ';
    1686                 :            :             }
    1687                 :            :         }
    1688                 :            :         Assert((*i).internal.get());
    1689                 :            :         // MatchNothing subqueries should have been removed by done(), and we
    1690                 :            :         // shouldn't get called before done() is, since that happens at the
    1691                 :            :         // end of the Xapian::Query constructor.
    1692 [ +  - ][ +  - ]:       7692 :         desc += (*i).internal->get_description();
                 [ +  - ]
    1693                 :            :     }
    1694         [ +  - ]:       2931 :     desc += ')';
    1695                 :       2931 :     return desc;
    1696                 :            : }
    1697                 :            : 
    1698                 :            : Query::Internal *
    1699                 :       1174 : QueryWindowed::done()
    1700                 :            : {
    1701                 :            :     // If window size not specified, default it.
    1702         [ +  + ]:       1174 :     if (window == 0)
    1703                 :        215 :         window = subqueries.size();
    1704                 :       1174 :     return QueryAndLike::done();
    1705                 :            : }
    1706                 :            : 
    1707                 :            : void
    1708                 :       1142 : QueryScaleWeight::gather_terms(void * void_terms) const
    1709                 :            : {
    1710                 :       1142 :     subquery.internal->gather_terms(void_terms);
    1711                 :       1142 : }
    1712                 :            : 
    1713                 :       8358 : void QueryTerm::serialise(string & result) const
    1714                 :            : {
    1715                 :       8358 :     size_t len = term.size();
    1716         [ +  + ]:       8358 :     if (len == 0) {
    1717 [ +  - ][ +  - ]:         10 :         if (wqf == 1 && pos == 0) {
    1718                 :            :             // Query::MatchAll
    1719                 :         10 :             result += '\x0f';
    1720                 :            :         } else {
    1721                 :            :             // Weird mutant versions of MatchAll
    1722                 :          0 :             result += '\x0e';
    1723         [ #  # ]:          0 :             result += encode_length(wqf);
    1724         [ #  # ]:         10 :             result += encode_length(pos);
    1725                 :            :         }
    1726         [ +  + ]:       8348 :     } else if (wqf == 1) {
    1727         [ +  + ]:       8342 :         if (pos == 0) {
    1728                 :            :             // Single occurrence free-text term without position set.
    1729         [ -  + ]:       7899 :             if (len >= 16) {
    1730                 :          0 :                 result += static_cast<char>(0x40 | 0x10);
    1731         [ #  # ]:          0 :                 result += encode_length(term.size() - 16);
    1732                 :            :             } else {
    1733                 :       7899 :                 result += static_cast<char>(0x40 | 0x10 | len);
    1734                 :            :             }
    1735                 :       7899 :             result += term;
    1736                 :            :         } else {
    1737                 :            :             // Single occurrence free-text term with position set.
    1738         [ -  + ]:        443 :             if (len >= 16) {
    1739                 :          0 :                 result += static_cast<char>(0x40 | 0x20);
    1740         [ #  # ]:          0 :                 result += encode_length(term.size() - 16);
    1741                 :            :             } else {
    1742                 :        443 :                 result += static_cast<char>(0x40 | 0x20 | len);
    1743                 :            :             }
    1744                 :        443 :             result += term;
    1745         [ +  - ]:       8342 :             result += encode_length(pos);
    1746                 :            :         }
    1747 [ -  + ][ #  # ]:          6 :     } else if (wqf > 1 || pos > 0) {
    1748                 :            :         // General case.
    1749         [ -  + ]:          6 :         if (len >= 16) {
    1750                 :          0 :             result += static_cast<char>(0x40 | 0x30);
    1751         [ #  # ]:          0 :             result += encode_length(term.size() - 16);
    1752         [ +  - ]:          6 :         } else if (len) {
    1753                 :          6 :             result += static_cast<char>(0x40 | 0x30 | len);
    1754                 :            :         }
    1755                 :          6 :         result += term;
    1756         [ +  - ]:          6 :         result += encode_length(wqf);
    1757         [ +  - ]:          6 :         result += encode_length(pos);
    1758                 :            :     } else {
    1759                 :            :         // Typical boolean term.
    1760                 :            :         AssertEq(wqf, 0);
    1761                 :            :         AssertEq(pos, 0);
    1762         [ #  # ]:          0 :         if (len >= 16) {
    1763                 :          0 :             result += static_cast<char>(0x40);
    1764         [ #  # ]:          0 :             result += encode_length(term.size() - 16);
    1765                 :            :         } else {
    1766                 :          0 :             result += static_cast<char>(0x40 | len);
    1767                 :            :         }
    1768                 :          0 :         result += term;
    1769                 :            :     }
    1770                 :       8358 : }
    1771                 :            : 
    1772                 :         45 : void QueryPostingSource::serialise(string & result) const
    1773                 :            : {
    1774                 :         45 :     result += static_cast<char>(0x0c);
    1775                 :            : 
    1776                 :         45 :     const string & n = source->name();
    1777 [ +  - ][ +  - ]:         45 :     result += encode_length(n.size());
    1778         [ +  - ]:         45 :     result += n;
    1779                 :            : 
    1780         [ +  + ]:         86 :     const string & s = source->serialise();
    1781 [ +  - ][ +  - ]:         41 :     result += encode_length(s.size());
    1782         [ +  - ]:         86 :     result += s;
    1783                 :         41 : }
    1784                 :            : 
    1785                 :        161 : void QueryScaleWeight::serialise(string & result) const
    1786                 :            : {
    1787                 :            :     Assert(subquery.internal.get());
    1788                 :        161 :     const string & s = serialise_double(scale_factor);
    1789         [ +  - ]:        161 :     result += '\x0d';
    1790         [ +  - ]:        161 :     result += s;
    1791         [ +  - ]:        161 :     subquery.internal->serialise(result);
    1792                 :        161 : }
    1793                 :            : 
    1794                 :            : struct is_matchnothing {
    1795                 :            :     bool operator()(const Xapian::Query & q) const {
    1796                 :            :         return q.internal.get() == NULL;
    1797                 :            :     }
    1798                 :            : };
    1799                 :            : 
    1800                 :            : void
    1801                 :       4035 : QueryAndLike::add_subquery(const Xapian::Query & subquery)
    1802                 :            : {
    1803                 :            :     // If the AndLike is already MatchNothing, do nothing.
    1804 [ +  - ][ +  + ]:       4035 :     if (subqueries.size() == 1 && subqueries[0].internal.get() == NULL)
         [ +  - ][ +  + ]
                 [ +  + ]
           [ +  +  #  # ]
    1805                 :       4035 :         return;
    1806                 :            :     // If we're adding MatchNothing, discard any previous subqueries.
    1807         [ +  + ]:       4031 :     if (subquery.internal.get() == NULL)
    1808                 :         54 :         subqueries.clear();
    1809                 :       4031 :     subqueries.push_back(subquery);
    1810                 :            : }
    1811                 :            : 
    1812                 :            : Query::Internal *
    1813                 :       1688 : QueryAndLike::done()
    1814                 :            : {
    1815                 :            :     // Empty AndLike gives MatchNothing.
    1816         [ -  + ]:       1688 :     if (subqueries.empty())
    1817                 :          0 :         return NULL;
    1818                 :            :     // We handle any subquery being MatchNothing in add_subquery() by leaving
    1819                 :            :     // a single MatchNothing subquery, and so this check results in AndLike
    1820                 :            :     // giving MatchNothing.
    1821         [ +  + ]:       1688 :     if (subqueries.size() == 1)
    1822                 :        132 :         return subqueries[0].internal.get();
    1823                 :       1556 :     return this;
    1824                 :            : }
    1825                 :            : 
    1826                 :            : PostList*
    1827                 :       1124 : QueryAndLike::postlist(QueryOptimiser * qopt, double factor) const
    1828                 :            : {
    1829                 :            :     LOGCALL(QUERY, PostList*, "QueryAndLike::postlist", qopt | factor);
    1830 [ +  - ][ +  - ]:       1124 :     AndContext ctx(qopt, subqueries.size());
    1831         [ +  - ]:       1124 :     postlist_sub_and_like(ctx, qopt, factor);
    1832         [ +  - ]:       1124 :     RETURN(ctx.postlist());
    1833                 :            : }
    1834                 :            : 
    1835                 :            : void
    1836                 :        428 : QueryAndLike::postlist_sub_and_like(AndContext& ctx, QueryOptimiser * qopt, double factor) const
    1837                 :            : {
    1838                 :        428 :     QueryVector::const_iterator i;
    1839 [ +  - ][ +  - ]:       1428 :     for (i = subqueries.begin(); i != subqueries.end(); ++i) {
         [ +  - ][ +  + ]
    1840                 :            :         // MatchNothing subqueries should have been removed by done().
    1841                 :            :         Assert((*i).internal.get());
    1842 [ +  - ][ +  - ]:       1000 :         (*i).internal->postlist_sub_and_like(ctx, qopt, factor);
    1843                 :            :     }
    1844                 :        428 : }
    1845                 :            : 
    1846                 :            : void
    1847                 :    1295970 : QueryOrLike::add_subquery(const Xapian::Query & subquery)
    1848                 :            : {
    1849                 :            :     // Drop any subqueries which are MatchNothing.
    1850         [ +  + ]:    1295970 :     if (subquery.internal.get() != NULL)
    1851                 :    1295897 :         subqueries.push_back(subquery);
    1852                 :    1295970 : }
    1853                 :            : 
    1854                 :            : Query::Internal *
    1855                 :     568673 : QueryOrLike::done()
    1856                 :            : {
    1857                 :            :     // An empty OrLike gives MatchNothing.  Note that add_subquery() drops any
    1858                 :            :     // subqueries which are MatchNothing.
    1859         [ +  + ]:     568673 :     if (subqueries.empty())
    1860                 :         20 :         return NULL;
    1861         [ +  + ]:     568653 :     if (subqueries.size() == 1)
    1862                 :      10491 :         return subqueries[0].internal.get();
    1863                 :     558162 :     return this;
    1864                 :            : }
    1865                 :            : 
    1866                 :            : void
    1867                 :        415 : QueryAndNot::add_subquery(const Xapian::Query & subquery)
    1868                 :            : {
    1869         [ +  + ]:        415 :     if (!subqueries.empty()) {
    1870                 :            :         // We're adding the 2nd or subsequent subquery, so this subquery is
    1871                 :            :         // negated.
    1872         [ +  + ]:        206 :         if (subqueries[0].internal.get() == NULL) {
    1873                 :            :             // The left side is already MatchNothing so drop any right side.
    1874                 :            :             //
    1875                 :            :             // MatchNothing AND_NOT X == MatchNothing
    1876                 :          2 :             return;
    1877                 :            :         }
    1878         [ +  + ]:        204 :         if (subquery.internal.get() == NULL) {
    1879                 :            :             // Drop MatchNothing on the right of AndNot.
    1880                 :            :             //
    1881                 :            :             // X AND_NOT MatchNothing == X
    1882                 :          1 :             return;
    1883                 :            :         }
    1884         [ +  + ]:        203 :         if (subquery.get_type() == subquery.OP_SCALE_WEIGHT) {
    1885                 :            :             // Strip OP_SCALE_WEIGHT wrapping from queries on the right of
    1886                 :            :             // AndNot as no weight is taken from them.
    1887         [ +  - ]:          3 :             subqueries.push_back(subquery.get_subquery(0));
    1888                 :            :             // The Query constructor for OP_SCALE_WEIGHT constructor should
    1889                 :            :             // eliminate OP_SCALE_WEIGHT applied to MatchNothing.
    1890                 :            :             Assert(subquery.get_subquery(0).internal.get() != NULL);
    1891                 :          3 :             return;
    1892                 :            :         }
    1893                 :            :     }
    1894                 :        415 :     subqueries.push_back(subquery);
    1895                 :            : }
    1896                 :            : 
    1897                 :            : Query::Internal *
    1898                 :        209 : QueryAndNot::done()
    1899                 :            : {
    1900                 :            :     // Any MatchNothing right subqueries get discarded by add_subquery() - if
    1901                 :            :     // that leaves just the left subquery, return that.
    1902                 :            :     //
    1903                 :            :     // If left subquery is MatchNothing, then add_subquery() discards all right
    1904                 :            :     // subqueries, so this check also gives MatchNothing for this case.
    1905         [ +  + ]:        209 :     if (subqueries.size() == 1)
    1906                 :          6 :         return subqueries[0].internal.get();
    1907                 :        203 :     return this;
    1908                 :            : }
    1909                 :            : 
    1910                 :            : void
    1911                 :        241 : QueryAndMaybe::add_subquery(const Xapian::Query & subquery)
    1912                 :            : {
    1913                 :            :     // If the left side of AndMaybe is already MatchNothing, do nothing.
    1914 [ +  - ][ +  + ]:        241 :     if (subqueries.size() == 1 && subqueries[0].internal.get() == NULL)
         [ +  - ][ +  + ]
                 [ +  + ]
           [ +  +  #  # ]
    1915                 :        241 :         return;
    1916                 :            :     // Drop any 2nd or subsequent subqueries which are MatchNothing.
    1917 [ +  + ][ +  + ]:        239 :     if (subquery.internal.get() != NULL || subqueries.empty())
                 [ +  + ]
    1918                 :        238 :         subqueries.push_back(subquery);
    1919                 :            : }
    1920                 :            : 
    1921                 :            : Query::Internal *
    1922                 :        122 : QueryAndMaybe::done()
    1923                 :            : {
    1924                 :            :     // Any MatchNothing right subqueries get discarded by add_subquery() - if
    1925                 :            :     // that leaves just the left subquery, return that.
    1926                 :            :     //
    1927                 :            :     // If left subquery is MatchNothing, then add_subquery() discards all right
    1928                 :            :     // subqueries, so this check also gives MatchNothing for this case.
    1929         [ +  + ]:        122 :     if (subqueries.size() == 1)
    1930                 :          6 :         return subqueries[0].internal.get();
    1931                 :        116 :     return this;
    1932                 :            : }
    1933                 :            : 
    1934                 :            : PostList*
    1935                 :     140029 : QueryOr::postlist(QueryOptimiser * qopt, double factor) const
    1936                 :            : {
    1937                 :            :     LOGCALL(QUERY, PostList*, "QueryOr::postlist", qopt | factor);
    1938 [ +  - ][ +  - ]:     140029 :     OrContext ctx(qopt, subqueries.size());
    1939         [ +  - ]:     140029 :     do_or_like(ctx, qopt, factor);
    1940         [ +  - ]:     140029 :     RETURN(ctx.postlist());
    1941                 :            : }
    1942                 :            : 
    1943                 :            : void
    1944                 :        142 : QueryOr::postlist_sub_or_like(OrContext& ctx, QueryOptimiser * qopt, double factor) const
    1945                 :            : {
    1946                 :        142 :     do_or_like(ctx, qopt, factor);
    1947                 :        142 : }
    1948                 :            : 
    1949                 :            : PostList*
    1950                 :        128 : QueryAndNot::postlist(QueryOptimiser * qopt, double factor) const
    1951                 :            : {
    1952                 :            :     LOGCALL(QUERY, PostList*, "QueryAndNot::postlist", qopt | factor);
    1953                 :            :     // FIXME: Combine and-like side with and-like stuff above.
    1954 [ +  - ][ +  - ]:        128 :     unique_ptr<PostList> l(subqueries[0].internal->postlist(qopt, factor));
    1955 [ +  - ][ +  - ]:        256 :     OrContext ctx(qopt, subqueries.size() - 1);
    1956         [ +  - ]:        128 :     do_or_like(ctx, qopt, 0.0, 0, 1);
    1957         [ +  - ]:        256 :     unique_ptr<PostList> r(ctx.postlist());
    1958         [ +  - ]:        256 :     RETURN(new AndNotPostList(l.release(), r.release(),
    1959                 :        128 :                               qopt->matcher, qopt->db_size));
    1960                 :            : }
    1961                 :            : 
    1962                 :            : PostList*
    1963                 :        112 : QueryXor::postlist(QueryOptimiser * qopt, double factor) const
    1964                 :            : {
    1965                 :            :     LOGCALL(QUERY, PostList*, "QueryXor::postlist", qopt | factor);
    1966 [ +  - ][ +  - ]:        112 :     XorContext ctx(qopt, subqueries.size());
    1967         [ +  - ]:        112 :     postlist_sub_xor(ctx, qopt, factor);
    1968         [ +  - ]:        112 :     RETURN(ctx.postlist());
    1969                 :            : }
    1970                 :            : 
    1971                 :            : void
    1972                 :        112 : QueryXor::postlist_sub_xor(XorContext& ctx, QueryOptimiser * qopt, double factor) const
    1973                 :            : {
    1974                 :        112 :     QueryVector::const_iterator i;
    1975 [ +  - ][ +  - ]:        424 :     for (i = subqueries.begin(); i != subqueries.end(); ++i) {
         [ +  - ][ +  + ]
    1976                 :            :         // MatchNothing subqueries should have been removed by done().
    1977                 :            :         Assert((*i).internal.get());
    1978 [ +  - ][ +  - ]:        312 :         (*i).internal->postlist_sub_xor(ctx, qopt, factor);
    1979                 :            :     }
    1980                 :        112 : }
    1981                 :            : 
    1982                 :            : PostList*
    1983                 :         68 : QueryAndMaybe::postlist(QueryOptimiser * qopt, double factor) const
    1984                 :            : {
    1985                 :            :     LOGCALL(QUERY, PostList*, "QueryAndMaybe::postlist", qopt | factor);
    1986                 :            :     // FIXME: Combine and-like side with and-like stuff above.
    1987 [ +  - ][ +  - ]:         68 :     unique_ptr<PostList> l(subqueries[0].internal->postlist(qopt, factor));
    1988         [ +  + ]:         68 :     if (factor == 0.0) {
    1989                 :            :         // An unweighted OP_AND_MAYBE can be replaced with its left branch.
    1990                 :         16 :         RETURN(l.release());
    1991                 :            :     }
    1992 [ +  - ][ +  - ]:        104 :     OrContext ctx(qopt, subqueries.size() - 1);
    1993         [ +  - ]:         52 :     do_or_like(ctx, qopt, factor, 0, 1);
    1994         [ +  - ]:        104 :     unique_ptr<PostList> r(ctx.postlist());
    1995         [ +  - ]:         52 :     RETURN(new AndMaybePostList(l.release(), r.release(),
    1996                 :         68 :                                 qopt->matcher, qopt->db_size));
    1997                 :            : }
    1998                 :            : 
    1999                 :            : PostList*
    2000                 :         50 : QueryFilter::postlist(QueryOptimiser * qopt, double factor) const
    2001                 :            : {
    2002                 :            :     LOGCALL(QUERY, PostList*, "QueryFilter::postlist", qopt | factor);
    2003                 :            :     // FIXME: Combine and-like stuff, like QueryOptimiser.
    2004                 :            :     AssertEq(subqueries.size(), 2);
    2005                 :            :     PostList * pls[2];
    2006 [ +  - ][ +  - ]:         50 :     unique_ptr<PostList> l(subqueries[0].internal->postlist(qopt, factor));
    2007 [ +  - ][ +  - ]:         50 :     pls[1] = subqueries[1].internal->postlist(qopt, 0.0);
    2008                 :         50 :     pls[0] = l.release();
    2009 [ +  - ][ +  - ]:         50 :     RETURN(new MultiAndPostList(pls, pls + 2, qopt->matcher, qopt->db_size));
    2010                 :            : }
    2011                 :            : 
    2012                 :            : void
    2013                 :          0 : QueryFilter::postlist_sub_and_like(AndContext& ctx, QueryOptimiser * qopt, double factor) const
    2014                 :            : {
    2015                 :          0 :     QueryVector::const_iterator i;
    2016 [ #  # ][ #  # ]:          0 :     for (i = subqueries.begin(); i != subqueries.end(); ++i) {
         [ #  # ][ #  # ]
    2017                 :            :         // MatchNothing subqueries should have been removed by done().
    2018                 :            :         Assert((*i).internal.get());
    2019 [ #  # ][ #  # ]:          0 :         (*i).internal->postlist_sub_and_like(ctx, qopt, factor);
    2020                 :            :         // Second and subsequent subqueries are unweighted.
    2021                 :          0 :         factor = 0.0;
    2022                 :            :     }
    2023                 :          0 : }
    2024                 :            : 
    2025                 :            : void
    2026                 :        748 : QueryWindowed::postlist_windowed(Query::op op, AndContext& ctx, QueryOptimiser * qopt, double factor) const
    2027                 :            : {
    2028         [ +  + ]:        748 :     if (!qopt->full_db_has_positions) {
    2029                 :            :         // No positional data anywhere, so just handle as AND.
    2030         [ +  - ]:         44 :         QueryAndLike::postlist_sub_and_like(ctx, qopt, factor);
    2031                 :        748 :         return;
    2032                 :            :     }
    2033                 :            : 
    2034 [ +  - ][ +  + ]:        704 :     if (!qopt->db.has_positions()) {
    2035                 :            :         // No positions in this subdatabase so this matches nothing, which
    2036                 :            :         // means the whole andcontext matches nothing.
    2037                 :            :         //
    2038                 :            :         // Bailing out here means we don't recurse deeper and that means we
    2039                 :            :         // don't call QueryOptimiser::inc_total_subqs() for leaf postlists in
    2040                 :            :         // the phrase, but at least one shard will count them, and the matcher
    2041                 :            :         // takes the highest answer (since 1.4.6).
    2042         [ +  - ]:         16 :         ctx.shrink(0);
    2043                 :         16 :         return;
    2044                 :            :     }
    2045                 :            : 
    2046                 :        688 :     bool old_need_positions = qopt->need_positions;
    2047                 :        688 :     qopt->need_positions = true;
    2048                 :            : 
    2049                 :        688 :     QueryVector::const_iterator i;
    2050 [ +  - ][ +  - ]:       2380 :     for (i = subqueries.begin(); i != subqueries.end(); ++i) {
         [ +  - ][ +  + ]
    2051                 :            :         // MatchNothing subqueries should have been removed by done().
    2052                 :            :         Assert((*i).internal.get());
    2053         [ +  - ]:       1692 :         bool is_term = ((*i).internal->get_type() == Query::LEAF_TERM);
    2054 [ +  - ][ +  - ]:       1692 :         PostList* pl = (*i).internal->postlist(qopt, factor);
    2055         [ +  + ]:       1692 :         if (!is_term)
    2056         [ +  - ]:        128 :             pl = new OrPosPostList(pl);
    2057         [ +  - ]:       1692 :         ctx.add_postlist(pl);
    2058                 :            :     }
    2059                 :            :     // Record the positional filter to apply higher up the tree.
    2060 [ +  - ][ +  - ]:        688 :     ctx.add_pos_filter(op, subqueries.size(), window);
    2061                 :            : 
    2062                 :        688 :     qopt->need_positions = old_need_positions;
    2063                 :            : }
    2064                 :            : 
    2065                 :            : void
    2066                 :        512 : QueryPhrase::postlist_sub_and_like(AndContext & ctx, QueryOptimiser * qopt, double factor) const
    2067                 :            : {
    2068                 :        512 :     QueryWindowed::postlist_windowed(Query::OP_PHRASE, ctx, qopt, factor);
    2069                 :        512 : }
    2070                 :            : 
    2071                 :            : void
    2072                 :        236 : QueryNear::postlist_sub_and_like(AndContext & ctx, QueryOptimiser * qopt, double factor) const
    2073                 :            : {
    2074                 :        236 :     QueryWindowed::postlist_windowed(Query::OP_NEAR, ctx, qopt, factor);
    2075                 :        236 : }
    2076                 :            : 
    2077                 :            : PostList*
    2078                 :        224 : QueryEliteSet::postlist(QueryOptimiser * qopt, double factor) const
    2079                 :            : {
    2080                 :            :     LOGCALL(QUERY, PostList*, "QueryEliteSet::postlist", qopt | factor);
    2081 [ +  - ][ +  - ]:        224 :     OrContext ctx(qopt, subqueries.size());
    2082         [ +  - ]:        224 :     do_or_like(ctx, qopt, factor, set_size);
    2083         [ +  - ]:        224 :     RETURN(ctx.postlist());
    2084                 :            : }
    2085                 :            : 
    2086                 :            : void
    2087                 :          0 : QueryEliteSet::postlist_sub_or_like(OrContext& ctx, QueryOptimiser * qopt, double factor) const
    2088                 :            : {
    2089                 :          0 :     do_or_like(ctx, qopt, factor, set_size);
    2090                 :          0 : }
    2091                 :            : 
    2092                 :            : PostList*
    2093                 :        650 : QuerySynonym::postlist(QueryOptimiser * qopt, double factor) const
    2094                 :            : {
    2095                 :            :     LOGCALL(QUERY, PostList*, "QuerySynonym::postlist", qopt | factor);
    2096                 :            :     // Save and restore total_subqs so we only add one for the whole
    2097                 :            :     // OP_SYNONYM subquery (or none if we're not weighted).
    2098                 :        650 :     Xapian::termcount save_total_subqs = qopt->get_total_subqs();
    2099         [ +  + ]:        650 :     if (factor != 0.0)
    2100                 :        602 :         ++save_total_subqs;
    2101                 :        650 :     PostList * pl = do_synonym(qopt, factor);
    2102                 :        650 :     qopt->set_total_subqs(save_total_subqs);
    2103                 :        650 :     RETURN(pl);
    2104                 :            : }
    2105                 :            : 
    2106                 :            : Query::Internal *
    2107                 :      51926 : QuerySynonym::done()
    2108                 :            : {
    2109                 :            :     // An empty Synonym gives MatchNothing.  Note that add_subquery() drops any
    2110                 :            :     // subqueries which are MatchNothing.
    2111         [ +  + ]:      51926 :     if (subqueries.empty())
    2112                 :          1 :         return NULL;
    2113         [ +  + ]:      51925 :     if (subqueries.size() == 1) {
    2114                 :      20785 :         Query::op sub_type = subqueries[0].get_type();
    2115                 :            :         // Synonym of a single subquery should only be simplified if that
    2116                 :            :         // subquery is a term (or MatchAll), or if it's also OP_SYNONYM.  Note
    2117                 :            :         // that MatchNothing subqueries are dropped, so we'd never get here
    2118                 :            :         // with a single MatchNothing subquery.
    2119 [ +  + ][ +  + ]:      20785 :         if (sub_type == Query::LEAF_TERM || sub_type == Query::LEAF_MATCH_ALL ||
                 [ -  + ]
    2120                 :            :             sub_type == Query::OP_SYNONYM) {
    2121                 :      20231 :             return subqueries[0].internal.get();
    2122                 :            :         }
    2123         [ +  - ]:        554 :         if (sub_type == Query::OP_WILDCARD) {
    2124                 :        554 :             auto q = static_cast<QueryWildcard*>(subqueries[0].internal.get());
    2125                 :            :             // SYNONYM over WILDCARD X -> WILDCARD SYNONYM for any combiner X.
    2126                 :        554 :             return q->change_combiner(Query::OP_SYNONYM);
    2127                 :            :         }
    2128                 :            :     }
    2129                 :      31140 :     return this;
    2130                 :            : }
    2131                 :            : 
    2132                 :            : PostList*
    2133                 :          8 : QueryMax::postlist(QueryOptimiser * qopt, double factor) const
    2134                 :            : {
    2135                 :            :     LOGCALL(QUERY, PostList*, "QueryMax::postlist", qopt | factor);
    2136                 :            :     // Save and restore total_subqs so we only add one for the whole
    2137                 :            :     // OP_MAX subquery (or none if we're not weighted).
    2138                 :          8 :     Xapian::termcount save_total_subqs = qopt->get_total_subqs();
    2139         [ +  - ]:          8 :     if (factor != 0.0)
    2140                 :          8 :         ++save_total_subqs;
    2141                 :          8 :     PostList * pl = do_max(qopt, factor);
    2142                 :          8 :     qopt->set_total_subqs(save_total_subqs);
    2143                 :          8 :     RETURN(pl);
    2144                 :            : }
    2145                 :            : 
    2146                 :            : Xapian::Query::op
    2147                 :        165 : QueryAnd::get_op() const
    2148                 :            : {
    2149                 :        165 :     return Xapian::Query::OP_AND;
    2150                 :            : }
    2151                 :            : 
    2152                 :            : Xapian::Query::op
    2153                 :       1871 : QueryOr::get_op() const
    2154                 :            : {
    2155                 :       1871 :     return Xapian::Query::OP_OR;
    2156                 :            : }
    2157                 :            : 
    2158                 :            : Xapian::Query::op
    2159                 :         93 : QueryAndNot::get_op() const
    2160                 :            : {
    2161                 :         93 :     return Xapian::Query::OP_AND_NOT;
    2162                 :            : }
    2163                 :            : 
    2164                 :            : Xapian::Query::op
    2165                 :         44 : QueryXor::get_op() const
    2166                 :            : {
    2167                 :         44 :     return Xapian::Query::OP_XOR;
    2168                 :            : }
    2169                 :            : 
    2170                 :            : Xapian::Query::op
    2171                 :         32 : QueryAndMaybe::get_op() const
    2172                 :            : {
    2173                 :         32 :     return Xapian::Query::OP_AND_MAYBE;
    2174                 :            : }
    2175                 :            : 
    2176                 :            : Xapian::Query::op
    2177                 :         14 : QueryFilter::get_op() const
    2178                 :            : {
    2179                 :         14 :     return Xapian::Query::OP_FILTER;
    2180                 :            : }
    2181                 :            : 
    2182                 :            : Xapian::Query::op
    2183                 :         69 : QueryNear::get_op() const
    2184                 :            : {
    2185                 :         69 :     return Xapian::Query::OP_NEAR;
    2186                 :            : }
    2187                 :            : 
    2188                 :            : Xapian::Query::op
    2189                 :        268 : QueryPhrase::get_op() const
    2190                 :            : {
    2191                 :        268 :     return Xapian::Query::OP_PHRASE;
    2192                 :            : }
    2193                 :            : 
    2194                 :            : Xapian::Query::op
    2195                 :        217 : QueryEliteSet::get_op() const
    2196                 :            : {
    2197                 :        217 :     return Xapian::Query::OP_ELITE_SET;
    2198                 :            : }
    2199                 :            : 
    2200                 :            : Xapian::Query::op
    2201                 :        145 : QuerySynonym::get_op() const
    2202                 :            : {
    2203                 :        145 :     return Xapian::Query::OP_SYNONYM;
    2204                 :            : }
    2205                 :            : 
    2206                 :            : Xapian::Query::op
    2207                 :          2 : QueryMax::get_op() const
    2208                 :            : {
    2209                 :          2 :     return Xapian::Query::OP_MAX;
    2210                 :            : }
    2211                 :            : 
    2212                 :            : Xapian::Query::op
    2213                 :          0 : QueryWildcard::get_op() const
    2214                 :            : {
    2215                 :          0 :     return Xapian::Query::OP_WILDCARD;
    2216                 :            : }
    2217                 :            : 
    2218                 :            : string
    2219                 :        188 : QueryAnd::get_description() const
    2220                 :            : {
    2221                 :        188 :     return get_description_helper(" AND ");
    2222                 :            : }
    2223                 :            : 
    2224                 :            : string
    2225                 :       1455 : QueryOr::get_description() const
    2226                 :            : {
    2227                 :       1455 :     return get_description_helper(" OR ");
    2228                 :            : }
    2229                 :            : 
    2230                 :            : string
    2231                 :        121 : QueryAndNot::get_description() const
    2232                 :            : {
    2233                 :        121 :     return get_description_helper(" AND_NOT ");
    2234                 :            : }
    2235                 :            : 
    2236                 :            : string
    2237                 :         44 : QueryXor::get_description() const
    2238                 :            : {
    2239                 :         44 :     return get_description_helper(" XOR ");
    2240                 :            : }
    2241                 :            : 
    2242                 :            : string
    2243                 :         95 : QueryAndMaybe::get_description() const
    2244                 :            : {
    2245                 :         95 :     return get_description_helper(" AND_MAYBE ");
    2246                 :            : }
    2247                 :            : 
    2248                 :            : string
    2249                 :         30 : QueryFilter::get_description() const
    2250                 :            : {
    2251                 :         30 :     return get_description_helper(" FILTER ");
    2252                 :            : }
    2253                 :            : 
    2254                 :            : string
    2255                 :         20 : QueryNear::get_description() const
    2256                 :            : {
    2257                 :         20 :     return get_description_helper(" NEAR ", window);
    2258                 :            : }
    2259                 :            : 
    2260                 :            : string
    2261                 :        431 : QueryPhrase::get_description() const
    2262                 :            : {
    2263                 :        431 :     return get_description_helper(" PHRASE ", window);
    2264                 :            : }
    2265                 :            : 
    2266                 :            : string
    2267                 :          1 : QueryEliteSet::get_description() const
    2268                 :            : {
    2269                 :          1 :     return get_description_helper(" ELITE_SET ", set_size);
    2270                 :            : }
    2271                 :            : 
    2272                 :            : string
    2273                 :        546 : QuerySynonym::get_description() const
    2274                 :            : {
    2275         [ -  + ]:        546 :     if (subqueries.size() == 1) {
    2276         [ #  # ]:          0 :         string d = "(SYNONYM ";
    2277 [ #  # ][ #  # ]:          0 :         d += subqueries[0].internal->get_description();
                 [ #  # ]
    2278         [ #  # ]:          0 :         d += ")";
    2279                 :          0 :         return d;
    2280                 :            :     }
    2281                 :        546 :     return get_description_helper(" SYNONYM ");
    2282                 :            : }
    2283                 :            : 
    2284                 :            : string
    2285                 :          0 : QueryMax::get_description() const
    2286                 :            : {
    2287                 :          0 :     return get_description_helper(" MAX ");
    2288                 :            : }
    2289                 :            : 
    2290                 :            : Xapian::Query::op
    2291                 :         96 : QueryInvalid::get_type() const XAPIAN_NOEXCEPT
    2292                 :            : {
    2293                 :         96 :     return Xapian::Query::OP_INVALID;
    2294                 :            : }
    2295                 :            : 
    2296                 :            : PostList*
    2297                 :          0 : QueryInvalid::postlist(QueryOptimiser *, double) const
    2298                 :            : {
    2299 [ #  # ][ #  # ]:          0 :     throw Xapian::InvalidOperationError("Query is invalid");
                 [ #  # ]
    2300                 :            : }
    2301                 :            : 
    2302                 :            : void
    2303                 :          0 : QueryInvalid::serialise(std::string & result) const
    2304                 :            : {
    2305                 :          0 :     result += static_cast<char>(0x00);
    2306                 :          0 : }
    2307                 :            : 
    2308                 :            : string
    2309                 :          0 : QueryInvalid::get_description() const
    2310                 :            : {
    2311         [ #  # ]:          0 :     return "<INVALID>";
    2312                 :            : }
    2313                 :            : 
    2314                 :            : }
    2315                 :            : }

Generated by: LCOV version 1.11