Skip to main content

lb_tantivy/query/
query.rs

1use std::fmt;
2
3use downcast_rs::impl_downcast;
4
5use super::bm25::Bm25StatisticsProvider;
6use super::Weight;
7use crate::core::searcher::Searcher;
8use crate::query::Explanation;
9use crate::schema::Schema;
10use crate::{DocAddress, Term};
11
12/// Argument used in `Query::weight(..)`
13#[derive(Copy, Clone)]
14pub enum EnableScoring<'a> {
15    /// Pass this to enable scoring.
16    Enabled {
17        /// The searcher to use during scoring.
18        searcher: &'a Searcher,
19
20        /// A [Bm25StatisticsProvider] used to compute BM25 scores.
21        ///
22        /// Normally this should be the [Searcher], but you can specify a custom
23        /// one to adjust the statistics.
24        statistics_provider: &'a dyn Bm25StatisticsProvider,
25    },
26    /// Pass this to disable scoring.
27    /// This can improve performance.
28    Disabled {
29        /// Schema is required.
30        schema: &'a Schema,
31        /// Searcher should be provided if available.
32        searcher_opt: Option<&'a Searcher>,
33    },
34}
35
36impl<'a> EnableScoring<'a> {
37    /// Create using [Searcher] with scoring enabled.
38    pub fn enabled_from_searcher(searcher: &'a Searcher) -> EnableScoring<'a> {
39        EnableScoring::Enabled {
40            searcher,
41            statistics_provider: searcher,
42        }
43    }
44
45    /// Create using a custom [Bm25StatisticsProvider] with scoring enabled.
46    pub fn enabled_from_statistics_provider(
47        statistics_provider: &'a dyn Bm25StatisticsProvider,
48        searcher: &'a Searcher,
49    ) -> EnableScoring<'a> {
50        EnableScoring::Enabled {
51            statistics_provider,
52            searcher,
53        }
54    }
55
56    /// Create using [Searcher] with scoring disabled.
57    pub fn disabled_from_searcher(searcher: &'a Searcher) -> EnableScoring<'a> {
58        EnableScoring::Disabled {
59            schema: searcher.schema(),
60            searcher_opt: Some(searcher),
61        }
62    }
63
64    /// Create using [Schema] with scoring disabled.
65    pub fn disabled_from_schema(schema: &'a Schema) -> EnableScoring<'a> {
66        Self::Disabled {
67            schema,
68            searcher_opt: None,
69        }
70    }
71
72    /// Returns the searcher if available.
73    pub fn searcher(&self) -> Option<&Searcher> {
74        match self {
75            EnableScoring::Enabled { searcher, .. } => Some(*searcher),
76            EnableScoring::Disabled { searcher_opt, .. } => searcher_opt.to_owned(),
77        }
78    }
79
80    /// Returns the schema.
81    pub fn schema(&self) -> &Schema {
82        match self {
83            EnableScoring::Enabled { searcher, .. } => searcher.schema(),
84            EnableScoring::Disabled { schema, .. } => schema,
85        }
86    }
87
88    /// Returns true if the scoring is enabled.
89    pub fn is_scoring_enabled(&self) -> bool {
90        matches!(self, EnableScoring::Enabled { .. })
91    }
92}
93
94/// The `Query` trait defines a set of documents and a scoring method
95/// for those documents.
96///
97/// The `Query` trait is in charge of defining :
98///
99/// - a set of documents
100/// - a way to score these documents
101///
102/// When performing a [search](Searcher::search), these documents will then
103/// be pushed to a [`Collector`](crate::collector::Collector),
104/// which will in turn be in charge of deciding what to do with them.
105///
106/// Concretely, this scored docset is represented by the
107/// [`Scorer`] trait.
108///
109/// Because our index is actually split into segments, the
110/// query does not actually directly creates [`DocSet`](crate::DocSet) object.
111/// Instead, the query creates a [`Weight`] object for a given searcher.
112///
113/// The weight object, in turn, makes it possible to create
114/// a scorer for a specific [`SegmentReader`].
115///
116/// So to sum it up :
117/// - a `Query` is a recipe to define a set of documents as well the way to score them.
118/// - a [`Weight`] is this recipe tied to a specific [`Searcher`]. It may for instance hold
119///   statistics about the different term of the query. It is created by the query.
120/// - a [`Scorer`] is a cursor over the set of matching documents, for a specific [`SegmentReader`].
121///   It is created by the [`Weight`].
122///
123/// When implementing a new type of `Query`, it is normal to implement a
124/// dedicated `Query`, [`Weight`] and [`Scorer`].
125///
126/// [`Scorer`]: crate::query::Scorer
127/// [`SegmentReader`]: crate::SegmentReader
128pub trait Query: QueryClone + Send + Sync + downcast_rs::Downcast + fmt::Debug {
129    /// Create the weight associated with a query.
130    ///
131    /// If scoring is not required, setting `scoring_enabled` to `false`
132    /// can increase performances.
133    ///
134    /// See [`Weight`].
135    fn weight(&self, enable_scoring: EnableScoring<'_>) -> crate::Result<Box<dyn Weight>>;
136
137    /// Returns an `Explanation` for the score of the document.
138    fn explain(&self, searcher: &Searcher, doc_address: DocAddress) -> crate::Result<Explanation> {
139        let weight = self.weight(EnableScoring::enabled_from_searcher(searcher))?;
140        let reader = searcher.segment_reader(doc_address.segment_ord);
141        weight.explain(reader, doc_address.doc_id)
142    }
143
144    /// Returns the number of documents matching the query.
145    fn count(&self, searcher: &Searcher) -> crate::Result<usize> {
146        let weight = self.weight(EnableScoring::disabled_from_searcher(searcher))?;
147        let mut result = 0;
148        for reader in searcher.segment_readers() {
149            result += weight.count(reader)? as usize;
150        }
151        Ok(result)
152    }
153
154    /// Extract all of the terms associated with the query and pass them to the
155    /// given closure.
156    ///
157    /// Each term is associated with a boolean indicating whether
158    /// positions are required or not.
159    ///
160    /// Note that there can be multiple instances of any given term
161    /// in a query and deduplication must be handled by the visitor.
162    fn query_terms<'a>(&'a self, _visitor: &mut dyn FnMut(&'a Term, bool)) {}
163}
164
165/// Implements `box_clone`.
166pub trait QueryClone {
167    /// Returns a boxed clone of `self`.
168    fn box_clone(&self) -> Box<dyn Query>;
169}
170
171impl<T> QueryClone for T
172where T: 'static + Query + Clone
173{
174    fn box_clone(&self) -> Box<dyn Query> {
175        Box::new(self.clone())
176    }
177}
178
179impl Query for Box<dyn Query> {
180    fn weight(&self, enabled_scoring: EnableScoring) -> crate::Result<Box<dyn Weight>> {
181        self.as_ref().weight(enabled_scoring)
182    }
183
184    fn count(&self, searcher: &Searcher) -> crate::Result<usize> {
185        self.as_ref().count(searcher)
186    }
187
188    fn query_terms<'a>(&'a self, visitor: &mut dyn FnMut(&'a Term, bool)) {
189        self.as_ref().query_terms(visitor);
190    }
191}
192
193impl QueryClone for Box<dyn Query> {
194    fn box_clone(&self) -> Box<dyn Query> {
195        self.as_ref().box_clone()
196    }
197}
198
199impl_downcast!(Query);