/** @file * @brief Querying session */ /* Copyright (C) 2005,2013,2016,2017,2024 Olly Betts * Copyright (C) 2009 Lemur Consulting Ltd * * 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 * . */ #ifndef XAPIAN_INCLUDED_ENQUIRE_H #define XAPIAN_INCLUDED_ENQUIRE_H #if !defined XAPIAN_IN_XAPIAN_H && !defined XAPIAN_LIB_BUILD # error Never use directly; include instead. #endif #include #include #include #include #include #include #include #include namespace Xapian { // Forward declarations of classes referenced below. class Database; class ExpandDecider; class KeyMaker; class MatchDecider; class MatchSpy; class Query; class RSet; class Weight; /** Querying session. * * An Enquire object represents a querying session - most of the options for * running a query can be set on it, and the query is run via * Enquire::get_mset(). */ class XAPIAN_VISIBILITY_DEFAULT Enquire { public: /// Class representing the Enquire internals. class Internal; /// @private @internal Reference counted internals. Xapian::Internal::intrusive_ptr_nonnull internal; /** Copying is allowed. * * The internals are reference counted, so copying is cheap. */ Enquire(const Enquire& o); /** Copying is allowed. * * The internals are reference counted, so assignment is cheap. */ Enquire& operator=(const Enquire& o); /// Move constructor. Enquire(Enquire&& o); /// Move assignment operator. Enquire& operator=(Enquire&& o); /** Constructor. * * @param db The database (or databases) to query. * * @since 2.0.0 If @a db has no subdatabases, it's handled like any other * empty database. In earlier versions, Xapian::InvalidArgumentError was * thrown in this case. */ explicit Enquire(const Database& db); /// Destructor. ~Enquire(); /** Set the query. * * If set_query() is not called before calling get_mset(), the default * query used will be Xapian::MatchNothing. * * @param query The Xapian::Query object * @param query_length The query length to use (default: * query.get_length()) */ void set_query(const Query& query, termcount query_length = 0); /** Get the currently set query. * * If set_query() is not called before calling get_query(), then the * default query Xapian::MatchNothing will be returned. */ const Query& get_query() const; /** Set the weighting scheme to use. * * The Xapian::Weight object passed is cloned by calling weight.clone(), * so doesn't need to remain valid after the call. * * If set_weighting_scheme() is not called before calling get_mset(), the * default weighting scheme is Xapian::BM25Weight(). * * @param weight Xapian::Weight object */ void set_weighting_scheme(const Weight& weight); /** Ordering of docids. * * Parameter to Enquire::set_docid_order(). */ typedef enum { /** docids sort in ascending order (default) */ ASCENDING = 1, /** docids sort in descending order. */ DESCENDING = 0, /** docids sort in whatever order is most efficient for the backend. */ DONT_CARE = 2 } docid_order; /** Set sort order for document IDs. * * This order only has an effect on documents which would otherwise * have equal rank. When ordering by relevance without a sort key, * this means documents with equal weight. For a boolean match * with no sort key, this means all documents. And if a sort key * is used, this means documents with the same sort key (and also equal * weight if ordering on relevance before or after the sort key). * * @param order This can be: * - Xapian::Enquire::ASCENDING * docids sort in ascending order (default) * - Xapian::Enquire::DESCENDING * docids sort in descending order * - Xapian::Enquire::DONT_CARE * docids sort in whatever order is most efficient for the backend * * Note: If you add documents in strict date order, then a boolean * search - i.e. set_weighting_scheme(Xapian::BoolWeight()) - with * set_docid_order(Xapian::Enquire::DESCENDING) is an efficient * way to perform "sort by date, newest first", and with * set_docid_order(Xapian::Enquire::ASCENDING) a very efficient way * to perform "sort by date, oldest first". */ void set_docid_order(docid_order order); /** Set the sorting to be by relevance only. * * This is the default. */ void set_sort_by_relevance(); /** Set the sorting to be by value only. * * Note that sorting by values uses a string comparison, so to use * this to sort by a numeric value you'll need to store the numeric * values in a manner which sorts appropriately. For example, you * could use Xapian::sortable_serialise() (which works for floating * point numbers as well as integers), or store numbers padded with * leading zeros or spaces, or with the number of digits prepended. * * @param sort_key value number to sort on. * * @param reverse If true, reverses the sort order. */ void set_sort_by_value(valueno sort_key, bool reverse); /** Set the sorting to be by key generated from values only. * * @param sorter The functor to use for generating keys. * * @param reverse If true, reverses the sort order. */ void set_sort_by_key(KeyMaker* sorter, bool reverse) XAPIAN_NONNULL(); /** Set the sorting to be by value, then by relevance for documents * with the same value. * * Note that sorting by values uses a string comparison, so to use * this to sort by a numeric value you'll need to store the numeric * values in a manner which sorts appropriately. For example, you * could use Xapian::sortable_serialise() (which works for floating * point numbers as well as integers), or store numbers padded with * leading zeros or spaces, or with the number of digits prepended. * * @param sort_key value number to sort on. * * @param reverse If true, reverses the sort order. */ void set_sort_by_value_then_relevance(valueno sort_key, bool reverse); /** Set the sorting to be by keys generated from values, then by * relevance for documents with identical keys. * * @param sorter The functor to use for generating keys. * * @param reverse If true, reverses the sort order. */ void set_sort_by_key_then_relevance(KeyMaker* sorter, bool reverse) XAPIAN_NONNULL(); /** Set the sorting to be by relevance then value. * * Note that sorting by values uses a string comparison, so to use * this to sort by a numeric value you'll need to store the numeric * values in a manner which sorts appropriately. For example, you * could use Xapian::sortable_serialise() (which works for floating * point numbers as well as integers), or store numbers padded with * leading zeros or spaces, or with the number of digits prepended. * * Note that with the default BM25 weighting scheme parameters, * non-identical documents will rarely have the same weight, so * this setting will give very similar results to * set_sort_by_relevance(). It becomes more useful with particular * BM25 parameter settings (e.g. BM25Weight(1,0,1,0,0)) or custom * weighting schemes. * * @param sort_key value number to sort on. * * @param reverse If true, reverses the sort order of sort_key. * Beware that in 1.2.16 and earlier, the sense * of this parameter was incorrectly inverted * and inconsistent with the other set_sort_by_... * methods. This was fixed in 1.2.17, so make that * version a minimum requirement if this detail * matters to your application. */ void set_sort_by_relevance_then_value(valueno sort_key, bool reverse); /** Set the sorting to be by relevance, then by keys generated from * values. * * Note that with the default BM25 weighting scheme parameters, * non-identical documents will rarely have the same weight, so * this setting will give very similar results to * set_sort_by_relevance(). It becomes more useful with particular * BM25 parameter settings (e.g. BM25Weight(1,0,1,0,0)) or custom * weighting schemes. * * @param sorter The functor to use for generating keys. * * @param reverse If true, reverses the sort order of the generated * keys. Beware that in 1.2.16 and earlier, the sense * of this parameter was incorrectly inverted * and inconsistent with the other set_sort_by_... * methods. This was fixed in 1.2.17, so make that * version a minimum requirement if this detail * matters to your application. */ void set_sort_by_relevance_then_key(KeyMaker* sorter, bool reverse) XAPIAN_NONNULL(); /** Control collapsing of results. * * The MSet returned by @a get_mset() will have only the "best" (at most) * @a collapse_max documents with each particular non-empty value in slot * @a collapse_key ("best" being highest ranked - i.e. highest weight or * highest sorting key). * * An example use might be to create a value for each document * containing an MD5 hash of the document contents. Then * duplicate documents from different sources can be eliminated at * search time by collapsing with @a collapse_max = 1 (it's better * to eliminate duplicates at index time, but this may not be * always be possible - for example the search may be over more * than one Xapian database). * * Another use is to group matches in a particular category (e.g. * you might collapse a mailing list search on the Subject: so * that there's only one result per discussion thread). In this * case you can use get_collapse_count() to give the user some * idea how many other results there are. And if you index the * Subject: as a boolean term as well as putting it in a value, * you can offer a link to a non-collapsed search restricted to * that thread using a boolean filter. * * @param collapse_key value slot to collapse on (default is * Xapian::BAD_VALUENO which means no collapsing). * * @param collapse_max Maximum number of documents with the same key * to allow (default: 1). */ void set_collapse_key(valueno collapse_key, doccount collapse_max = 1); /** Set lower bounds on percentage and/or weight. * * @param percent_threshold Lower bound on percentage score * @param weight_threshold Lower bound on weight (default: 0) * * No thresholds are applied by default, and if either threshold is set * to 0, then that threshold is disabled. */ void set_cutoff(int percent_threshold, double weight_threshold = 0); /** Add a matchspy. * * This matchspy will be called with some of the documents which match * the query, during the match process. Exactly which of the matching * documents are passed to it depends on exactly when certain * optimisations occur during the match process, but it can be * controlled to some extent by setting the @a checkatleast parameter * to @a get_mset(). * * In particular, if there are enough matching documents, at least the * number specified by @a checkatleast will be passed to the matchspy. * This means that you can force the matchspy to be shown all matching * documents by setting @a checkatleast to the number of documents in * the database. * * @param spy The MatchSpy subclass to add. The caller must * ensure that this remains valid while the Enquire * object remains active, or until @a * clear_matchspies() is called, or else allocate * the MatchSpy object with new and then disown it by * calling spy->release() before passing it in. */ void add_matchspy(MatchSpy* spy) XAPIAN_NONNULL(); /** Remove all the matchspies. */ void clear_matchspies(); /** Set a time limit for the match. * * Matches with check_at_least set high can take a long time in some * cases. You can set a time limit on this, after which check_at_least * will be turned off. * * @param time_limit time in seconds after which to disable * check_at_least (default: 0.0 which means no * time limit) * * Limitations: * * This feature is currently supported on platforms which support POSIX * interval timers. Interaction with the remote backend when using * multiple databases may have bugs. There's not currently a way to * force the match to end after a certain time. */ void set_time_limit(double time_limit); /** Run the query. * * Run the query using the settings in this Enquire object and those * passed as parameters to the method, and return a Xapian::MSet object. * * @param first Zero-based index of the first result to return * (which supports retrieving pages of results). * @param maxitems The maximum number of documents to return. * @param checkatleast Check at least this many documents. By default * Xapian will avoiding considering documents * which it can prove can't match, which is faster * but can result in a loose bounds on and a poor * estimate of the total number of matches - * setting checkatleast higher allows trading off * speed for tighter bounds and a more accurate * estimate. (default: 0) * @param rset Documents marked as relevant (default: no * documents have been marked as relevant) * @param mdecider Xapian::MatchDecider object - this acts as a * yes/no filter on documents which match the * query. See also Xapian::PostingSource. * (default: no Xapian::MatchDecider) */ MSet get_mset(doccount first, doccount maxitems, doccount checkatleast = 0, const RSet* rset = NULL, const MatchDecider* mdecider = NULL) const; /** Run the query. * * Run the query using the settings in this Enquire object and those * passed as parameters to the method, and return a Xapian::MSet object. * * @param first Zero-based index of the first result to return * (which supports retrieving pages of results). * @param maxitems The maximum number of documents to return. * @param rset Documents marked as relevant (default: no * documents have been marked as relevant) * @param mdecider Xapian::MatchDecider object - this acts as a * yes/no filter on documents which match the * query. See also Xapian::PostingSource. * (default: no Xapian::MatchDecider) */ MSet get_mset(doccount first, doccount maxitems, const RSet* rset, const MatchDecider* mdecider = NULL) const { return get_mset(first, maxitems, 0, rset, mdecider); } /** Iterate query terms matching a document. * * Takes terms from the query set by @a set_query() and from the document * with document ID @a did in the database set in the constructor, and * returns terms which are in both, ordered by ascending query position. * Terms which occur more than once in the query are only returned once, * at the lowest term position they occur at. * * @param did Document ID in the database set in the constructor */ TermIterator get_matching_terms_begin(docid did) const; /** Iterate query terms matching a document. * * Convenience overloaded form, taking a Xapian::MSetIterator instead * of a Xapian::docid. * * @param it MSetIterator to return matching terms for */ TermIterator get_matching_terms_begin(const MSetIterator& it) const { return get_matching_terms_begin(*it); } /// End iterator corresponding to @a get_matching_terms_begin(). TermIterator get_matching_terms_end(docid) const noexcept { return TermIterator(); } /// End iterator corresponding to @a get_matching_terms_begin(). TermIterator get_matching_terms_end(const MSetIterator&) const noexcept { return TermIterator(); } /** Set the weighting scheme to use for expansion. * * If you don't call this method, the default is as if you'd used: * * set_expansion_scheme("prob"); * * @param eweightname A string in lowercase specifying the name of * the scheme to be used. The following schemes * are currently available: * * "bo1": Bose-Einstein 1 model from the Divergence * From Randomness framework. * * "prob" : Probabilistic model (since 1.4.26). * * "trad" : Deprecated alias for "prob". * @param expand_k Parameter k for probabilistic query expansion. * A default value of 1.0 is used if none is specified. */ void set_expansion_scheme(std::string_view eweightname, double expand_k = 1.0) const; /** Flag telling get_eset() to allow query terms in Xapian::ESet. * * By default, query terms are excluded. This is appropriate when using * get_eset() to generate terms for query expansion, but for some other * uses query terms are also interesting. */ static const int INCLUDE_QUERY_TERMS = 1; /** Flag telling get_eset() to always use the exact term frequency. * * By default, get_eset() approximates the term frequency in some cases * (currently when we're expanding from more than one database and there * are sub-databases which don't contain any documents marked as * relevant). This is faster and should still return good results, but * this flag allows the exact term frequency to always be used. */ static const int USE_EXACT_TERMFREQ = 2; /** Perform query expansion. * * Perform query expansion using a Xapian::RSet indicating some documents * which are relevant (typically based on the user marking results or * similar). * * @param maxitems The maximum number of terms to return. * @param rset Documents marked as relevant. * @param flags Bitwise-or combination of @a * INCLUDE_QUERY_TERMS and @a USE_EXACT_TERMFREQ * flags (default: 0). * @param edecider Xapian::ExpandDecider object - this acts as a * yes/no filter on terms which are being * considered. (default: no * Xapian::ExpandDecider) * @param min_weight Lower bound on weight of acceptable terms * (default: 0.0) * * @return Xapian::ESet object containing a list of terms with weights. */ ESet get_eset(termcount maxitems, const RSet& rset, int flags = 0, const ExpandDecider* edecider = NULL, double min_weight = 0.0) const; /** Perform query expansion. * * Perform query expansion using a Xapian::RSet indicating some documents * which are relevant (typically based on the user marking results or * similar). * * @param maxitems The maximum number of terms to return. * @param rset Documents marked as relevant. * @param edecider Xapian::ExpandDecider object - this acts as a * yes/no filter on terms which are being considered. * * @return Xapian::ESet object containing a list of terms with weights. */ ESet get_eset(termcount maxitems, const RSet& rset, const ExpandDecider* edecider) const { return get_eset(maxitems, rset, 0, edecider); } /// Return a string describing this object. std::string get_description() const; }; } #endif // XAPIAN_INCLUDED_ENQUIRE_H