/** @file
 * @brief Xapian::Query API class
 */
/* Copyright (C) 2011-2026 Olly Betts
 * Copyright (C) 2008 Richard Boulton
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see
 * <https://www.gnu.org/licenses/>.
 */

#ifndef XAPIAN_INCLUDED_QUERY_H
#define XAPIAN_INCLUDED_QUERY_H

#if !defined XAPIAN_IN_XAPIAN_H && !defined XAPIAN_LIB_BUILD
# error Never use <xapian/query.h> directly; include <xapian.h> instead.
#endif

#include <string>
#include <string_view>

#include <xapian/attributes.h>
#include <xapian/intrusive_ptr.h>
#include <xapian/postingiterator.h>
#include <xapian/registry.h>
#include <xapian/termiterator.h>
#include <xapian/types.h>
#include <xapian/visibility.h>

namespace Xapian {

class PostingSource;

/// Class representing a query.
class XAPIAN_VISIBILITY_DEFAULT Query {
  public:
    /// Class representing the query internals.
    class Internal;
    /// @private @internal Reference counted internals.
    Xapian::Internal::intrusive_ptr<Internal> internal;

    /** A query matching no documents.
     *
     *  This is a static instance of a default-constructed Xapian::Query
     *  object.  It is safe to use concurrently from different threads,
     *  unlike @a MatchAll (this is because MatchNothing has a NULL
     *  internal object so there's no reference counting happening).
     *
     *  When combined with other Query objects using the various supported
     *  operators, MatchNothing works like @c false in boolean logic, so
     *  <code>MatchNothing & q</code> is @c MatchNothing, while
     *  <code>MatchNothing | q</code> is @c q.
     */
    static const Xapian::Query MatchNothing;

    /** A query matching all documents.
     *
     *  This is a static instance of
     *  <code>Xapian::Query(std::string_view())</code>.
     *  If you are constructing Query objects which use @a MatchAll in
     *  different threads then the reference counting of the static object can
     *  get messed up by concurrent access so you should instead use
     *  <code>Xapian::Query(std::string_view())</code> directly.
     */
    static const Xapian::Query MatchAll;

    /** Query operators. */
    enum op {
        /** Match only documents which all subqueries match.
         *
         *  When used in a weighted context, the weight is the sum of the
         *  weights for all the subqueries.
         */
        OP_AND = 0,

        /** Match documents which at least one subquery matches.
         *
         *  When used in a weighted context, the weight is the sum of the
         *  weights for matching subqueries (so additional matching subqueries
         *  will mean a higher weight).
         */
        OP_OR = 1,

        /** Match documents which the first subquery matches but no others do.
         *
         *  When used in a weighted context, the weight is just the weight of
         *  the first subquery.
         */
        OP_AND_NOT = 2,

        /** Match documents which an odd number of subqueries match.
         *
         *  When used in a weighted context, the weight is the sum of the
         *  weights for matching subqueries (so additional matching subqueries
         *  will mean a higher weight).
         */
        OP_XOR = 3,

        /** Match the first subquery taking extra weight from other subqueries.
         *
         *  When used in a weighted context, the weight is the sum of the
         *  weights for matching subqueries (so additional matching subqueries
         *  will mean a higher weight).
         *
         *  Because only the first subquery determines which documents are
         *  matched, in a non-weighted context only the first subquery matters.
         */
        OP_AND_MAYBE = 4,

        /** Match like OP_AND but only taking weight from the first subquery.
         *
         *  When used in a non-weighted context, OP_FILTER and OP_AND are
         *  equivalent.
         *
         *  In older 1.4.x, the third and subsequent subqueries were ignored
         *  in some situations.  This was fixed in 1.4.15.
         */
        OP_FILTER = 5,

        /** Match only documents where all subqueries match near each other.
         *
         *  The subqueries must match at term positions within the specified
         *  window size, in any order.
         *
         *  Currently subqueries must be terms or terms composed with OP_OR.
         *
         *  When used in a weighted context, the weight is the sum of the
         *  weights for all the subqueries.
         */
        OP_NEAR = 6,

        /** Match only documents where all subqueries match near and in order.
         *
         *  The subqueries must match at term positions within the specified
         *  window size, in the same term position order as subquery order.
         *
         *  Currently subqueries must be terms or terms composed with OP_OR.
         *
         *  When used in a weighted context, the weight is the sum of the
         *  weights for all the subqueries.
         */
        OP_PHRASE = 7,

        /** Match only documents where a value slot is within a given range.
         *
         *  This operator never contributes weight.
         */
        OP_VALUE_RANGE = 8,

        /** Scale the weight contributed by a subquery.
         *
         *  The weight is the weight of the subquery multiplied by the
         *  specified non-negative scale factor (so if the scale factor is
         *  zero then the subquery contributes no weight).
         */
        OP_SCALE_WEIGHT = 9,

        /** Pick the best N subqueries and combine with OP_OR.
         *
         *  If you want to implement a feature which finds documents similar to
         *  a piece of text, an obvious approach is to build an "OR" query from
         *  all the terms in the text, and run this query against a database
         *  containing the documents.  However such a query can contain a lots
         *  of terms and be quite slow to perform, yet many of these terms
         *  don't contribute usefully to the results.
         *
         *  The OP_ELITE_SET operator can be used instead of OP_OR in this
         *  situation.  OP_ELITE_SET selects the most important ''N'' terms and
         *  then acts as an OP_OR query with just these, ignoring any other
         *  terms.  This will usually return results just as good as the full
         *  OP_OR query, but much faster.
         *
         *  In general, the OP_ELITE_SET operator can be used when you have a
         *  large OR query, but it doesn't matter if the search completely
         *  ignores some of the less important terms in the query.
         *
         *  The subqueries don't have to be terms.  If they aren't then
         *  OP_ELITE_SET could potentially pick a subset which doesn't
         *  actually match any documents even if the full OR would match some
         *  (because OP_ELITE_SET currently selects those subqueries which can
         *  return the highest weights).  This is probably rare in practice
         *  though.
         *
         *  You can specify a parameter to the query constructor which controls
         *  the number of subqueries which OP_ELITE_SET will pick.  If not
         *  specified, this defaults to 10 (Xapian used to default to
         *  <code>ceil(sqrt(number_of_subqueries))</code> if there are more
         *  than 100 subqueries, but this rather arbitrary special case was
         *  dropped in 1.3.0).  For example, this will pick the best 7 terms:
         *
         *  <pre>
         *  Xapian::Query query(Xapian::Query::OP_ELITE_SET, subqs.begin(), subqs.end(), 7);
         *  </pre>
         *
         *  If the number of subqueries is less than this threshold,
         *  OP_ELITE_SET behaves identically to OP_OR.
         *
         *  When used with a sharded database, OP_ELITE_SET currently picks
         *  the subqueries to use separately for each shard based on the
         *  maximum weight they can return in that shard.  This means it
         *  probably won't select exactly the same terms, and so the results
         *  of the search may not be exactly the same as for a single database
         *  with equivalent contents.
         */
        OP_ELITE_SET = 10,

        /** Match only documents where a value slot is >= a given value.
         *
         *  Similar to @a OP_VALUE_RANGE, but open-ended.
         *
         *  This operator never contributes weight.
         */
        OP_VALUE_GE = 11,

        /** Match only documents where a value slot is <= a given value.
         *
         *  Similar to @a OP_VALUE_RANGE, but open-ended.
         *
         *  This operator never contributes weight.
         */
        OP_VALUE_LE = 12,

        /** Match like OP_OR but weighting as if a single term.
         *
         *  The weight is calculated combining the statistics for the
         *  subqueries to approximate the weight of a single term occurring
         *  with those statistics.
         */
        OP_SYNONYM = 13,

        /** Pick the maximum weight of any subquery.
         *
         *  Matches the same documents as @a OP_OR, but the weight contributed
         *  is the maximum weight from any matching subquery (for OP_OR, it's
         *  the sum of the weights from the matching subqueries).
         *
         *  @since Added in Xapian 1.3.2.
         */
        OP_MAX = 14,

        /** Wildcard expansion.
         *
         *  @since Added in Xapian 1.3.3.
         */
        OP_WILDCARD = 15,

        /** Edit distance expansion.
         *
         *  Expand to terms within a specified edit distance of a target.
         *
         *  This works in a fairly similar way to OP_WILDCARD - the difference
         *  is that instead of expanding to terms matching a wildcard pattern,
         *  it expands to terms within a specified number of edits (insertion,
         *  deletion or substitution of a character, or transposition of two
         *  adjacent characters) of a specified target.
         *
         *  @since Added in Xapian 2.0.0.
         */
        OP_EDIT_DISTANCE = 16,

        /** Construct an invalid query.
         *
         *  This can be useful as a placeholder - for example @a RangeProcessor
         *  uses it as a return value to indicate that a range hasn't been
         *  recognised.
         */
        OP_INVALID = 99,

        /** Value returned by get_type() for a term. */
        LEAF_TERM = 100,

        /** Value returned by get_type() for a PostingSource. */
        LEAF_POSTING_SOURCE,

        /** Value returned by get_type() for MatchAll or equivalent.
         *
         *  This is returned for any
         *  <code>Xapian::Query(std::string_view())</code> object.
         */
        LEAF_MATCH_ALL,

        /** Value returned by get_type() for MatchNothing or equivalent.
         *
         *  This is returned for any <code>Xapian::Query()</code> object.
         */
        LEAF_MATCH_NOTHING
    };

    enum {
        /** Throw an error if OP_WILDCARD exceeds its expansion limit.
         *
         *  Xapian::WildcardError will be thrown when the query is actually
         *  run.
         */
        WILDCARD_LIMIT_ERROR = 0x00,
        /** Stop expanding when OP_WILDCARD reaches its expansion limit.
         *
         *  This makes the wildcard expand to only the first N terms (sorted
         *  by byte order).
         */
        WILDCARD_LIMIT_FIRST = 0x01,
        /** Limit OP_WILDCARD expansion to the most frequent terms.
         *
         *  If OP_WILDCARD would expand to more than its expansion limit, the
         *  most frequent terms are taken.  This approach works well for cases
         *  such as expanding a partial term at the end of a query string which
         *  the user hasn't finished typing yet - as well as being less expense
         *  to evaluate than the full expansion, using only the most frequent
         *  terms tends to give better results too.
         */
        WILDCARD_LIMIT_MOST_FREQUENT = 0x02,

        /** @private @internal */
        WILDCARD_LIMIT_MASK_ = 0x03,

        /** Support * which matches 0 or more characters.
         *
         *  @since Added in Xapian 2.0.0
         */
        WILDCARD_PATTERN_MULTI = 0x10,

        /** Support ? which matches a single character.
         *
         *  @since Added in Xapian 2.0.0.
         */
        WILDCARD_PATTERN_SINGLE = 0x20,

        /** Enable all supported glob-like features.
         *
         *  @since Added in Xapian 2.0.0.
         */
        WILDCARD_PATTERN_GLOB = WILDCARD_PATTERN_MULTI|WILDCARD_PATTERN_SINGLE
    };

    /** Construct a query matching no documents.
     *
     *  @a MatchNothing is a static instance of this.
     *
     *  When combined with other Query objects using the various supported
     *  operators, <code>Query()</code> works like @c false in boolean logic,
     *  so <code>Query() & q</code> is @c Query(), while
     *  <code>Query() | q</code> is @c q.
     */
    Query() noexcept { }

    /// Destructor.
    ~Query() { }

    /** Copying is allowed.
     *
     *  The internals are reference counted, so copying is cheap.
     */
    Query(const Query & o) : internal(o.internal) { }

    /** Copying is allowed.
     *
     *  The internals are reference counted, so assignment is cheap.
     */
    Query & operator=(const Query & o) { internal = o.internal; return *this; }

    /// Move constructor.
    Query(Query &&) = default;

    /// Move assignment operator.
    Query & operator=(Query &&) = default;

    /** Construct a Query object for a term.
     *
     *  @param term The term.  An empty string constructs a query matching
     *              all documents (@a MatchAll is a static instance of this).
     *  @param wqf  The within-query frequency. (default: 1)
     *  @param pos  The query position.  Currently this is mainly used to
     *              determine the order of terms obtained via
     *              get_terms_begin(). (default: 0)
     */
    Query(const std::string& term,
          Xapian::termcount wqf = 1,
          Xapian::termpos pos = 0)
        : Query(std::string_view(term), wqf, pos)
    { }

    /** Construct a Query object for a term.
     *
     *  @param term The term.  An empty string constructs a query matching
     *              all documents (@a MatchAll is a static instance of this).
     *  @param wqf  The within-query frequency. (default: 1)
     *  @param pos  The query position.  Currently this is mainly used to
     *              determine the order of terms obtained via
     *              get_terms_begin(). (default: 0)
     */
    Query(const char* term,
          Xapian::termcount wqf = 1,
          Xapian::termpos pos = 0)
        : Query(std::string_view(term), wqf, pos)
    { }

    /** Construct a Query object for a term.
     *
     *  @param term The term.  An empty string constructs a query matching
     *              all documents (@a MatchAll is a static instance of this).
     *  @param wqf  The within-query frequency. (default: 1)
     *  @param pos  The query position.  Currently this is mainly used to
     *              determine the order of terms obtained via
     *              get_terms_begin(). (default: 0)
     */
    Query(std::string_view term,
          Xapian::termcount wqf = 1,
          Xapian::termpos pos = 0);

    /** Construct a Query object for a PostingSource. */
    explicit Query(Xapian::PostingSource * source);

    /** Scale using OP_SCALE_WEIGHT.
     *
     *  @param factor Non-negative real number to multiply weights by.
     *  @param subquery Query object to scale weights from.
     */
    Query(double factor, const Xapian::Query & subquery);

    /** Scale using OP_SCALE_WEIGHT.
     *
     *  In this form, the op_ parameter is totally redundant - use
     *  Query(factor, subquery) in preference.
     *
     *  @param op_      Must be OP_SCALE_WEIGHT.
     *  @param factor   Non-negative real number to multiply weights by.
     *  @param subquery Query object to scale weights from.
     */
    Query(op op_, const Xapian::Query & subquery, double factor);

    /** Construct a Query object by combining two others.
     *
     *  @param op_      The operator to combine the queries with.
     *  @param a        First subquery.
     *  @param b        Second subquery.
     */
    Query(op op_, const Xapian::Query & a, const Xapian::Query & b)
    {
        init(op_, 2);
        bool positional = (op_ == OP_NEAR || op_ == OP_PHRASE);
        add_subquery(positional, a);
        add_subquery(positional, b);
        done();
    }

    /** Construct a Query object by combining two terms.
     *
     *  @param op_  The operator to combine the terms with.
     *  @param a    First term.
     *  @param b    Second term.
     */
    template<typename S1, typename S2,
        typename
        std::enable_if<std::is_constructible<std::string_view, S1>::value &&
                       std::is_constructible<std::string_view, S2>::value,
                       bool>::type = true>
    Query(op op_, S1 a, S2 b)
    {
        init(op_, 2);
        add_subquery(false, std::string_view(a));
        add_subquery(false, std::string_view(b));
        done();
    }

    /** Construct a Query object for a single-ended value range.
     *
     *  @param op_          Must be OP_VALUE_LE or OP_VALUE_GE currently.
     *  @param slot         The value slot to work over.
     *  @param range_limit  The limit of the range.
     */
    Query(op op_, Xapian::valueno slot, std::string_view range_limit);

    /** Construct a Query object for a value range.
     *
     *  @param op_          Must be OP_VALUE_RANGE currently.
     *  @param slot         The value slot to work over.
     *  @param range_lower  Lower end of the range.
     *  @param range_upper  Upper end of the range.
     */
    Query(op op_, Xapian::valueno slot,
          std::string_view range_lower, std::string_view range_upper);

    /** Query constructor for OP_EDIT_DISTANCE and OP_WILDCARD queries.
     *
     *  @param op_      Must be OP_EDIT_DISTANCE or OP_WILDCARD
     *  @param pattern  The wildcard pattern (for OP_WILDCARD) or target string
     *                  (for OP_EDIT_DISTANCE).  See @a flags which affects
     *                  how this pattern is interpreted.
     *  @param max_expansion    The maximum number of terms to expand to
     *                          (default: 0, which means no limit)
     *  @param flags    Flags controlling aspects of the wildcarding - this
     *                  consists of a bitwise OR of:
     *
     *                  * At most one of @a WILDCARD_LIMIT_ERROR (the default),
     *                    @a WILDCARD_LIMIT_FIRST or
     *                    @a WILDCARD_LIMIT_MOST_FREQUENT specifying how to
     *                    enforce max_expansion.
     *
     *                    When searching multiple databases, the expansion
     *                    limit is currently applied independently for each
     *                    database, so the total number of terms may be higher
     *                    than the limit.  This is arguably a bug, and may
     *                    change in future versions.
     *
     *                  * For OP_WILDCARD: Zero or more of
     *                    @a WILDCARD_PATTERN_MULTI and
     *                    @a WILDCARD_PATTERN_SINGLE, which specify whether
     *                    '*' (matching zero or more characters) and '?'
     *                    (matching exactly one character) are supported.
     *                    If neither is specified, then a Xapian-1.4-compatible
     *                    mode is used where the pattern matches terms which
     *                    start with the pattern interpreted as a literal
     *                    string.
     *
     *  @param combiner The @a Query::op to combine the terms with - one of
     *                  @a OP_SYNONYM (the default), @a OP_OR or @a OP_MAX.
     *
     *  For OP_WILDCARD: A leading wildcard won't match terms starting with an
     *  ASCII capital letter, as this is assumed to be part of a term prefix.
     */
    Query(op op_,
          std::string_view pattern,
          Xapian::termcount max_expansion = 0,
          int flags = WILDCARD_LIMIT_ERROR,
          op combiner = OP_SYNONYM);

    /** Query constructor for OP_EDIT_DISTANCE queries.
     *
     *  This form supports some additional parameters.
     *
     *  @param op_      Must be OP_EDIT_DISTANCE.
     *  @param pattern  The target string.
     *  @param max_expansion    The maximum number of terms to expand to
     *                          (default: 0, which means no limit)
     *  @param flags    Flags controlling aspects of the wildcarding:
     *
     *                  * At most one of @a WILDCARD_LIMIT_ERROR (the default),
     *                    @a WILDCARD_LIMIT_FIRST or
     *                    @a WILDCARD_LIMIT_MOST_FREQUENT specifying how to
     *                    enforce max_expansion.
     *
     *                    When searching multiple databases, the expansion
     *                    limit is currently applied independently for each
     *                    database, so the total number of terms may be higher
     *                    than the limit.  This is arguably a bug, and may
     *                    change in future versions.
     *
     *  @param combiner The @a Query::op to combine the terms with - one of
     *                  @a OP_SYNONYM (the default), @a OP_OR or @a OP_MAX.
     *  @param edit_distance
     *                  The maximum number of edits allowed between a term
     *                  and @a target (an edit is insertion, deletion or
     *                  substitution of a character, or transposition of two
     *                  adjacent characters).  Default: 2
     *  @param min_prefix_len
     *                  The length in bytes of any initial substring of target
     *                  that is required to match exactly.  Default: 0
     *
     *  @since Added in Xapian 2.0.0.
     */
    Query(op op_,
          std::string_view pattern,
          Xapian::termcount max_expansion,
          int flags,
          op combiner,
          unsigned edit_distance,
          size_t min_prefix_len = 0);

    /** Construct a Query object from a begin/end iterator pair.
     *
     *  Dereferencing the iterator should return a Xapian::Query, a non-NULL
     *  Xapian::Query*, a std::string_view or a type which converts to one of
     *  these (e.g. std::string or const char*).
     *
     *  If begin == end then there are no subqueries and the resulting Query
     *  won't match anything.
     *
     *  @param op_      The operator to combine the queries with.
     *  @param begin    Begin iterator.
     *  @param end      End iterator.
     *  @param window   Window size for OP_NEAR and OP_PHRASE, or 0 to use the
     *                  number of subqueries as the window size (default: 0).
     */
    template<typename I,
        typename std::enable_if<
            std::is_convertible<typename std::iterator_traits<I>::value_type,
                                Xapian::Query>::value ||
            std::is_convertible<typename std::iterator_traits<I>::value_type,
                                Xapian::Query*>::value ||
            std::is_convertible<typename std::iterator_traits<I>::value_type,
                                std::string_view>::value,
            bool>::type = true,
        typename iterator_category =
            typename std::iterator_traits<I>::iterator_category>
    Query(op op_, I begin, I end, Xapian::termcount window = 0)
    {
        if (begin != end) {
            init(op_, window, begin, end, iterator_category());
            bool positional = (op_ == OP_NEAR || op_ == OP_PHRASE);
            for (I i = begin; i != end; ++i) {
                add_subquery(positional, *i);
            }
            done();
        }
    }

#ifdef SWIG
    // SWIG's %template doesn't seem to handle a templated ctor so we
    // provide this fake specialised form of the above prototype.
    Query(op op_, XapianSWIGQueryItor qbegin, XapianSWIGQueryItor qend,
          Xapian::termcount parameter = 0);

# ifdef SWIGJAVA
    Query(op op_, XapianSWIGStrItor qbegin, XapianSWIGStrItor qend,
          Xapian::termcount parameter = 0);
# endif
#endif

    /** Begin iterator for terms in the query object.
     *
     *  The iterator returns terms in ascending query position order, and
     *  will return the same term in each unique position it occurs in.
     *  If you want the terms in sorted order and without duplicates, see
     *  get_unique_terms_begin().
     */
    const TermIterator get_terms_begin() const;

    /// End iterator for terms in the query object.
    const TermIterator get_terms_end() const noexcept {
        return TermIterator();
    }

    /** Begin iterator for unique terms in the query object.
     *
     *  Terms are sorted and terms with the same name removed from the list.
     *
     *  If you want the terms in ascending query position order, see
     *  get_terms_begin().
     */
    const TermIterator get_unique_terms_begin() const;

    /// End iterator for unique terms in the query object.
    const TermIterator get_unique_terms_end() const noexcept {
        return TermIterator();
    }

    /** Return the length of this query object. */
    Xapian::termcount get_length() const noexcept XAPIAN_PURE_FUNCTION;

    /** Check if this query is Xapian::Query::MatchNothing. */
    bool empty() const noexcept {
        return !internal;
    }

    /** Serialise this object into a string. */
    std::string serialise() const;

    /** Unserialise a string and return a Query object.
     *
     *  @param serialised   the string to unserialise.
     *  @param reg          Xapian::Registry object to use to unserialise
     *                      user-subclasses of Xapian::PostingSource
     *                      (default: standard registry).
     */
    static const Query unserialise(std::string_view serialised,
                                   const Registry & reg = Registry());

    /** Get the type of the top level of the query. */
    op get_type() const noexcept XAPIAN_PURE_FUNCTION;

    /** Get the number of subqueries of the top level query. */
    size_t get_num_subqueries() const noexcept XAPIAN_PURE_FUNCTION;

    /** Get the wqf parameter of a leaf node.
     *
     *  @since Added in Xapian 2.0.0.
     */
    Xapian::termcount get_leaf_wqf() const;

    /** Get the pos parameter of a leaf node.
     *
     *  @since Added in Xapian 2.0.0.
     */
    Xapian::termpos get_leaf_pos() const;

    /** Read a top level subquery.
      *
      * @param n  Return the n-th subquery (starting from 0) - only valid when
      *           0 <= n < get_num_subqueries().
      */
    const Query get_subquery(size_t n) const;

    /// Return a string describing this object.
    std::string get_description() const;

    /** Combine with another Xapian::Query object using OP_AND.
     *
     *  @since Since Xapian 1.4.10, when called on a Query object which is
     *  OP_AND and has a reference count of 1, then @a o is appended as a new
     *  subquery (provided @a o is a different Query object and
     *  <code>!o.empty()</code>).
     */
    const Query operator&=(const Query & o);

    /** Combine with another Xapian::Query object using OP_OR.
     *
     *  @since Since Xapian 1.4.10, when called on a Query object which is
     *  OP_OR and has a reference count of 1, then @a o is appended as a new
     *  subquery (provided @a o is a different Query object and
     *  <code>!o.empty()</code>).
     */
    const Query operator|=(const Query & o);

    /** Combine with another Xapian::Query object using OP_XOR.
     *
     *  @since Since Xapian 1.4.10, when called on a Query object which is
     *  OP_XOR and has a reference count of 1, then @a o is appended as a new
     *  subquery (provided @a o is a different Query object and
     *  <code>!o.empty()</code>).
     */
    const Query operator^=(const Query & o);

    /** Scale using OP_SCALE_WEIGHT.
     *
     *  @param factor Non-negative real number to multiply weights by.
     */
    const Query operator*=(double factor) {
        return (*this = Query(factor, *this));
    }

    /** Inverse scale using OP_SCALE_WEIGHT.
     *
     *  @param factor Positive real number to divide weights by.
     */
    const Query operator/=(double factor) {
        return (*this = Query(1.0 / factor, *this));
    }

    /// @private @internal Wrap an existing Internal.
    explicit Query(Internal * internal_) : internal(internal_) { }

    /** Construct with just an operator.
     *
     *  @param op_ The operator to use - currently only OP_INVALID is useful.
     */
    explicit Query(Query::op op_) {
        init(op_, 0);
        if (op_ != Query::OP_INVALID) done();
    }

  private:
    void init(Query::op op_, size_t n_subqueries, Xapian::termcount window = 0);

    template<typename I>
    void init(Query::op op_, Xapian::termcount window,
              const I & begin, const I & end, std::random_access_iterator_tag)
    {
        init(op_, end - begin, window);
    }

    template<typename I>
    void init(Query::op op_, Xapian::termcount window,
              const I &, const I &, std::input_iterator_tag)
    {
        init(op_, 0, window);
    }

    void add_subquery(bool positional, const Xapian::Query & subquery);

    void add_subquery(bool, const std::string& subquery) {
        add_subquery(false, Xapian::Query(subquery));
    }

    void add_subquery(bool, const char* subquery) {
        add_subquery(false, Xapian::Query(subquery));
    }

    void add_subquery(bool, std::string_view subquery) {
        add_subquery(false, Xapian::Query(subquery));
    }

    void add_subquery(bool positional, const Xapian::Query * subquery) {
        // FIXME: subquery NULL?
        add_subquery(positional, *subquery);
    }

    void done();
};

/** Combine two Xapian::Query objects using OP_AND. */
inline const Query
operator&(const Query & a, const Query & b)
{
    return Query(Query::OP_AND, a, b);
}

/** Combine two Xapian::Query objects using OP_OR. */
inline const Query
operator|(const Query & a, const Query & b)
{
    return Query(Query::OP_OR, a, b);
}

/** Combine two Xapian::Query objects using OP_XOR. */
inline const Query
operator^(const Query & a, const Query & b)
{
    return Query(Query::OP_XOR, a, b);
}

/** Scale a Xapian::Query object using OP_SCALE_WEIGHT.
 *
 *  @param factor Non-negative real number to multiply weights by.
 *  @param q Xapian::Query object.
 */
inline const Query
operator*(double factor, const Query & q)
{
    return Query(factor, q);
}

/** Scale a Xapian::Query object using OP_SCALE_WEIGHT.
 *
 *  @param q Xapian::Query object.
 *  @param factor Non-negative real number to multiply weights by.
 */
inline const Query
operator*(const Query & q, double factor)
{
    return Query(factor, q);
}

/** Inverse-scale a Xapian::Query object using OP_SCALE_WEIGHT.
 *
 *  @param factor Positive real number to divide weights by.
 *  @param q Xapian::Query object.
 */
inline const Query
operator/(const Query & q, double factor)
{
    return Query(1.0 / factor, q);
}

/** @private @internal */
class InvertedQuery_ {
    const Query & query;

    void operator=(const InvertedQuery_ &);

    explicit InvertedQuery_(const Query & query_) : query(query_) { }

  public:
    // GCC 4.2 seems to needs a copy ctor.
    InvertedQuery_(const InvertedQuery_ & o) : query(o.query) { }

    operator Query() const {
        return Query(Query::OP_AND_NOT, Query(std::string_view{}), query);
    }

    friend const InvertedQuery_ operator~(const Query &q);

    friend const Query operator&(const Query & a, const InvertedQuery_ & b);

    friend const Query operator&=(Query & a, const InvertedQuery_ & b);
};

/** Combine two Xapian::Query objects using OP_AND_NOT.
 *
 *  E.g. Xapian::Query q = q1 &~ q2;
 */
inline const Query
operator&(const Query & a, const InvertedQuery_ & b)
{
    return Query(Query::OP_AND_NOT, a, b.query);
}

/** Combine two Xapian::Query objects using OP_AND_NOT with result in the first.
 *
 *  E.g. q1 &=~ q2;
 */
inline const Query
operator&=(Query & a, const InvertedQuery_ & b)
{
    return (a = Query(Query::OP_AND_NOT, a, b.query));
}

#ifndef DOXYGEN /* @internal doesn't seem to avoid a warning here. */
/** @internal Helper to allow q1 &~ q2 to work. */
inline const InvertedQuery_
operator~(const Query &q)
{
    return InvertedQuery_(q);
}
#endif

namespace Internal {
class AndContext;
class OrContext;
class XorContext;

struct PostListAndEstimate;
class QueryOptimiser;
struct TermFreqs;
}

/** @private @internal */
class Query::Internal : public Xapian::Internal::intrusive_base {
  public:
    Internal() noexcept { }

    virtual ~Internal();

    XAPIAN_VISIBILITY_INTERNAL
    virtual Xapian::Internal::PostListAndEstimate
    postlist(Xapian::Internal::QueryOptimiser* qopt,
             double factor,
             Xapian::Internal::TermFreqs* termfreqs) const = 0;

    XAPIAN_VISIBILITY_INTERNAL
    virtual bool
    postlist_sub_and_like(Xapian::Internal::AndContext& ctx,
                          Xapian::Internal::QueryOptimiser* qopt,
                          double factor,
                          Xapian::Internal::TermFreqs* termfreqs) const;

    XAPIAN_VISIBILITY_INTERNAL
    virtual void
    postlist_sub_bool_or_like(Xapian::Internal::OrContext& ctx,
                              Xapian::Internal::QueryOptimiser* qopt,
                              Xapian::Internal::TermFreqs* termfreqs) const;

    XAPIAN_VISIBILITY_INTERNAL
    virtual void
    postlist_sub_or_like(Xapian::Internal::OrContext& ctx,
                         Xapian::Internal::QueryOptimiser* qopt,
                         double factor,
                         Xapian::Internal::TermFreqs* termfreqs,
                         bool keep_zero_weight = true) const;

    XAPIAN_VISIBILITY_INTERNAL
    virtual void
    postlist_sub_xor(Xapian::Internal::XorContext& ctx,
                     Xapian::Internal::QueryOptimiser* qopt,
                     double factor,
                     Xapian::Internal::TermFreqs* termfreqs) const;

    XAPIAN_VISIBILITY_INTERNAL
    virtual termcount get_length() const noexcept XAPIAN_PURE_FUNCTION;

    XAPIAN_VISIBILITY_INTERNAL
    virtual void serialise(std::string & result) const = 0;

    XAPIAN_VISIBILITY_INTERNAL
    static Query::Internal* unserialise(const char** p, const char* end,
                                        const Registry& reg);

    XAPIAN_VISIBILITY_INTERNAL
    virtual Query::op get_type() const noexcept XAPIAN_PURE_FUNCTION = 0;
    XAPIAN_VISIBILITY_INTERNAL
    virtual size_t get_num_subqueries() const noexcept XAPIAN_PURE_FUNCTION;
    XAPIAN_VISIBILITY_INTERNAL
    virtual const Query get_subquery(size_t n) const;
    XAPIAN_VISIBILITY_INTERNAL
    virtual termcount get_wqf() const;
    XAPIAN_VISIBILITY_INTERNAL
    virtual termpos get_pos() const;

    XAPIAN_VISIBILITY_INTERNAL
    virtual std::string get_description() const = 0;

    // Pass argument as void* to avoid need to include <vector>.
    XAPIAN_VISIBILITY_INTERNAL
    virtual void gather_terms(void * void_terms) const;
};

inline const Query
Query::operator&=(const Query & o)
{
    if (o.empty()) {
        // q &= empty_query sets q to empty_query.
        *this = o;
    } else if (this != &o &&
               internal &&
               internal->_refs == 1 &&
               get_type() == OP_AND) {
        // Appending a subquery to an existing AND.
        add_subquery(false, o);
    } else {
        *this = Query(OP_AND, *this, o);
    }
    return *this;
}

inline const Query
Query::operator|=(const Query & o)
{
    if (o.empty()) {
        // q |= empty_query is a no-op.
    } else if (this != &o &&
               internal &&
               internal->_refs == 1 &&
               get_type() == OP_OR) {
        // Appending a subquery to an existing OR.
        add_subquery(false, o);
    } else {
        *this = Query(OP_OR, *this, o);
    }
    return *this;
}

inline const Query
Query::operator^=(const Query & o)
{
    if (o.empty()) {
        // q ^= empty_query is a no-op.
    } else if (internal.get() == o.internal.get()) {
        // q ^= q gives MatchNothing.
        internal = NULL;
    } else if (internal &&
               internal->_refs == 1 &&
               get_type() == OP_XOR) {
        // Appending a subquery to an existing XOR.
        add_subquery(false, o);
    } else {
        *this = Query(OP_XOR, *this, o);
    }
    return *this;
}

}

#endif // XAPIAN_INCLUDED_QUERY_H
