summavy/query/
query.rs

1use std::fmt;
2
3use async_trait::async_trait;
4use downcast_rs::impl_downcast;
5
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(&'a Searcher),
17    /// Pass this to disable scoring.
18    /// This can improve performance.
19    Disabled {
20        /// Schema is required.
21        schema: &'a Schema,
22        /// Searcher should be provided if available.
23        searcher_opt: Option<&'a Searcher>,
24    },
25}
26
27impl<'a> EnableScoring<'a> {
28    /// Create using [Searcher] with scoring enabled.
29    pub fn enabled_from_searcher(searcher: &'a Searcher) -> EnableScoring<'a> {
30        EnableScoring::Enabled(searcher)
31    }
32
33    /// Create using [Searcher] with scoring disabled.
34    pub fn disabled_from_searcher(searcher: &'a Searcher) -> EnableScoring<'a> {
35        EnableScoring::Disabled {
36            schema: searcher.schema(),
37            searcher_opt: Some(searcher),
38        }
39    }
40
41    /// Create using [Schema] with scoring disabled.
42    pub fn disabled_from_schema(schema: &'a Schema) -> EnableScoring<'a> {
43        Self::Disabled {
44            schema,
45            searcher_opt: None,
46        }
47    }
48
49    /// Returns the searcher if available.
50    pub fn searcher(&self) -> Option<&Searcher> {
51        match self {
52            EnableScoring::Enabled(searcher) => Some(searcher),
53            EnableScoring::Disabled { searcher_opt, .. } => searcher_opt.to_owned(),
54        }
55    }
56
57    /// Returns the schema.
58    pub fn schema(&self) -> &Schema {
59        match self {
60            EnableScoring::Enabled(searcher) => searcher.schema(),
61            EnableScoring::Disabled { schema, .. } => schema,
62        }
63    }
64
65    /// Returns true if the scoring is enabled.
66    pub fn is_scoring_enabled(&self) -> bool {
67        matches!(self, EnableScoring::Enabled(..))
68    }
69}
70
71/// The `Query` trait defines a set of documents and a scoring method
72/// for those documents.
73///
74/// The `Query` trait is in charge of defining :
75///
76/// - a set of documents
77/// - a way to score these documents
78///
79/// When performing a [search](Searcher::search), these documents will then
80/// be pushed to a [`Collector`](crate::collector::Collector),
81/// which will in turn be in charge of deciding what to do with them.
82///
83/// Concretely, this scored docset is represented by the
84/// [`Scorer`] trait.
85///
86/// Because our index is actually split into segments, the
87/// query does not actually directly creates [`DocSet`](crate::DocSet) object.
88/// Instead, the query creates a [`Weight`] object for a given searcher.
89///
90/// The weight object, in turn, makes it possible to create
91/// a scorer for a specific [`SegmentReader`].
92///
93/// So to sum it up :
94/// - a `Query` is a recipe to define a set of documents as well the way to score them.
95/// - a [`Weight`] is this recipe tied to a specific [`Searcher`]. It may for instance
96/// hold statistics about the different term of the query. It is created by the query.
97/// - a [`Scorer`] is a cursor over the set of matching documents, for a specific
98/// [`SegmentReader`]. It is created by the [`Weight`].
99///
100/// When implementing a new type of `Query`, it is normal to implement a
101/// dedicated `Query`, [`Weight`] and [`Scorer`].
102///
103/// [`Scorer`]: crate::query::Scorer
104/// [`SegmentReader`]: crate::SegmentReader
105#[async_trait]
106pub trait Query: QueryClone + Send + Sync + downcast_rs::Downcast + fmt::Debug {
107    /// Create the weight associated with a query.
108    ///
109    /// If scoring is not required, setting `scoring_enabled` to `false`
110    /// can increase performances.
111    ///
112    /// See [`Weight`].
113    fn weight(&self, enable_scoring: EnableScoring<'_>) -> crate::Result<Box<dyn Weight>>;
114
115    /// Create the weight associated with a query asynchronously.
116    ///
117    /// See [`Self::weight`].
118    #[cfg(feature = "quickwit")]
119    async fn weight_async(
120        &self,
121        enable_scoring: EnableScoring<'_>,
122    ) -> crate::Result<Box<dyn Weight>>;
123
124    /// Returns an `Explanation` for the score of the document.
125    fn explain(&self, searcher: &Searcher, doc_address: DocAddress) -> crate::Result<Explanation> {
126        let weight = self.weight(EnableScoring::enabled_from_searcher(searcher))?;
127        let reader = searcher.segment_reader(doc_address.segment_ord);
128        weight.explain(reader, doc_address.doc_id)
129    }
130
131    /// Returns the number of documents matching the query.
132    fn count(&self, searcher: &Searcher) -> crate::Result<usize> {
133        let weight = self.weight(EnableScoring::disabled_from_searcher(searcher))?;
134        let mut result = 0;
135        for reader in searcher.segment_readers() {
136            result += weight.count(reader)? as usize;
137        }
138        Ok(result)
139    }
140
141    /// Returns the number of documents matching the query.
142    #[cfg(feature = "quickwit")]
143    async fn count_async(&self, searcher: &Searcher) -> crate::Result<usize> {
144        let weight = self
145            .weight_async(EnableScoring::disabled_from_schema(searcher.schema()))
146            .await?;
147        let mut result = 0;
148        for reader in searcher.segment_readers() {
149            result += weight.count_async(reader).await? 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
179#[async_trait]
180impl Query for Box<dyn Query> {
181    fn weight(&self, enabled_scoring: EnableScoring) -> crate::Result<Box<dyn Weight>> {
182        self.as_ref().weight(enabled_scoring)
183    }
184
185    #[cfg(feature = "quickwit")]
186    async fn weight_async(
187        &self,
188        enable_scoring: EnableScoring<'_>,
189    ) -> crate::Result<Box<dyn Weight>> {
190        self.as_ref().weight_async(enable_scoring).await
191    }
192
193    fn count(&self, searcher: &Searcher) -> crate::Result<usize> {
194        self.as_ref().count(searcher)
195    }
196
197    fn query_terms<'a>(&'a self, visitor: &mut dyn FnMut(&'a Term, bool)) {
198        self.as_ref().query_terms(visitor);
199    }
200}
201
202impl QueryClone for Box<dyn Query> {
203    fn box_clone(&self) -> Box<dyn Query> {
204        self.as_ref().box_clone()
205    }
206}
207
208impl_downcast!(Query);