Skip to main content

lb_tantivy/collector/
mod.rs

1//! # Collectors
2//!
3//! Collectors define the information you want to extract from the documents matching the queries.
4//! In tantivy jargon, we call this information your search "fruit".
5//!
6//! Your fruit could for instance be :
7//! - [the count of matching documents](crate::collector::Count)
8//! - [the top 10 documents, by relevancy or by a fast field](crate::collector::TopDocs)
9//! - [facet counts](FacetCollector)
10//!
11//! At some point in your code, you will trigger the actual search operation by calling
12//! [`Searcher::search()`](crate::Searcher::search).
13//! This call will look like this:
14//!
15//! ```verbatim
16//! let fruit = searcher.search(&query, &collector)?;
17//! ```
18//!
19//! Here the type of fruit is actually determined as an associated type of the collector
20//! (`Collector::Fruit`).
21//!
22//!
23//! # Combining several collectors
24//!
25//! A rich search experience often requires to run several collectors on your search query.
26//! For instance,
27//! - selecting the top-K products matching your query
28//! - counting the matching documents
29//! - computing several facets
30//! - computing statistics about the matching product prices
31//!
32//! A simple and efficient way to do that is to pass your collectors as one tuple.
33//! The resulting `Fruit` will then be a typed tuple with each collector's original fruits
34//! in their respective position.
35//!
36//! ```rust
37//! # use tantivy::schema::*;
38//! # use tantivy::*;
39//! # use tantivy::query::*;
40//! use tantivy::collector::{Count, TopDocs};
41//! #
42//! # fn main() -> tantivy::Result<()> {
43//! # let mut schema_builder = Schema::builder();
44//! #     let title = schema_builder.add_text_field("title", TEXT);
45//! #     let schema = schema_builder.build();
46//! #     let index = Index::create_in_ram(schema);
47//! #     let mut index_writer = index.writer(15_000_000)?;
48//! #       index_writer.add_document(doc!(
49//! #       title => "The Name of the Wind",
50//! #      ))?;
51//! #     index_writer.add_document(doc!(
52//! #        title => "The Diary of Muadib",
53//! #     ))?;
54//! #     index_writer.commit()?;
55//! #     let reader = index.reader()?;
56//! #     let searcher = reader.searcher();
57//! #     let query_parser = QueryParser::for_index(&index, vec![title]);
58//! #     let query = query_parser.parse_query("diary")?;
59//! let (doc_count, top_docs): (usize, Vec<(Score, DocAddress)>) =
60//! searcher.search(&query, &(Count, TopDocs::with_limit(2)))?;
61//! #     Ok(())
62//! # }
63//! ```
64//!
65//! The `Collector` trait is implemented for up to 4 collectors.
66//! If you have more than 4 collectors, you can either group them into
67//! tuples of tuples `(a,(b,(c,d)))`, or rely on [`MultiCollector`].
68//!
69//! # Combining several collectors dynamically
70//!
71//! Combining collectors into a tuple is a zero-cost abstraction: everything
72//! happens as if you had manually implemented a single collector
73//! combining all of our features.
74//!
75//! Unfortunately it requires you to know at compile time your collector types.
76//! If on the other hand, the collectors depend on some query parameter,
77//! you can rely on [`MultiCollector`]'s.
78//!
79//!
80//! # Implementing your own collectors.
81//!
82//! See the `custom_collector` example.
83
84use downcast_rs::impl_downcast;
85
86use crate::{DocId, Score, SegmentOrdinal, SegmentReader};
87
88mod count_collector;
89pub use self::count_collector::Count;
90
91mod histogram_collector;
92pub use histogram_collector::HistogramCollector;
93
94mod multi_collector;
95pub use self::multi_collector::{FruitHandle, MultiCollector, MultiFruit};
96
97mod top_collector;
98
99mod top_score_collector;
100pub use self::top_collector::ComparableDoc;
101pub use self::top_score_collector::{TopDocs, TopNComputer};
102
103mod custom_score_top_collector;
104pub use self::custom_score_top_collector::{CustomScorer, CustomSegmentScorer};
105
106mod tweak_score_top_collector;
107pub use self::tweak_score_top_collector::{ScoreSegmentTweaker, ScoreTweaker};
108mod facet_collector;
109pub use self::facet_collector::{FacetCollector, FacetCounts};
110use crate::query::Weight;
111
112mod docset_collector;
113pub use self::docset_collector::DocSetCollector;
114
115mod filter_collector_wrapper;
116pub use self::filter_collector_wrapper::{BytesFilterCollector, FilterCollector};
117
118/// `Fruit` is the type for the result of our collection.
119/// e.g. `usize` for the `Count` collector.
120pub trait Fruit: Send + downcast_rs::Downcast {}
121
122impl<T> Fruit for T where T: Send + downcast_rs::Downcast {}
123
124/// Collectors are in charge of collecting and retaining relevant
125/// information from the document found and scored by the query.
126///
127/// For instance,
128///
129/// - keeping track of the top 10 best documents
130/// - computing a breakdown over a fast field
131/// - computing the number of documents matching the query
132///
133/// Our search index is in fact a collection of segments, so
134/// a `Collector` trait is actually more of a factory to instance
135/// `SegmentCollector`s for each segments.
136///
137/// The collection logic itself is in the `SegmentCollector`.
138///
139/// Segments are not guaranteed to be visited in any specific order.
140pub trait Collector: Sync + Send {
141    /// `Fruit` is the type for the result of our collection.
142    /// e.g. `usize` for the `Count` collector.
143    type Fruit: Fruit;
144
145    /// Type of the `SegmentCollector` associated with this collector.
146    type Child: SegmentCollector;
147
148    /// `set_segment` is called before beginning to enumerate
149    /// on this segment.
150    fn for_segment(
151        &self,
152        segment_local_id: SegmentOrdinal,
153        segment: &SegmentReader,
154    ) -> crate::Result<Self::Child>;
155
156    /// Returns true iff the collector requires to compute scores for documents.
157    fn requires_scoring(&self) -> bool;
158
159    /// Combines the fruit associated with the collection of each segments
160    /// into one fruit.
161    fn merge_fruits(
162        &self,
163        segment_fruits: Vec<<Self::Child as SegmentCollector>::Fruit>,
164    ) -> crate::Result<Self::Fruit>;
165
166    /// Created a segment collector and
167    fn collect_segment(
168        &self,
169        weight: &dyn Weight,
170        segment_ord: u32,
171        reader: &SegmentReader,
172    ) -> crate::Result<<Self::Child as SegmentCollector>::Fruit> {
173        let mut segment_collector = self.for_segment(segment_ord, reader)?;
174
175        match (reader.alive_bitset(), self.requires_scoring()) {
176            (Some(alive_bitset), true) => {
177                weight.for_each(reader, &mut |doc, score| {
178                    if alive_bitset.is_alive(doc) {
179                        segment_collector.collect(doc, score);
180                    }
181                })?;
182            }
183            (Some(alive_bitset), false) => {
184                weight.for_each_no_score(reader, &mut |docs| {
185                    for doc in docs.iter().cloned() {
186                        if alive_bitset.is_alive(doc) {
187                            segment_collector.collect(doc, 0.0);
188                        }
189                    }
190                })?;
191            }
192            (None, true) => {
193                weight.for_each(reader, &mut |doc, score| {
194                    segment_collector.collect(doc, score);
195                })?;
196            }
197            (None, false) => {
198                weight.for_each_no_score(reader, &mut |docs| {
199                    segment_collector.collect_block(docs);
200                })?;
201            }
202        }
203
204        Ok(segment_collector.harvest())
205    }
206}
207
208impl<TSegmentCollector: SegmentCollector> SegmentCollector for Option<TSegmentCollector> {
209    type Fruit = Option<TSegmentCollector::Fruit>;
210
211    fn collect(&mut self, doc: DocId, score: Score) {
212        if let Some(segment_collector) = self {
213            segment_collector.collect(doc, score);
214        }
215    }
216
217    fn harvest(self) -> Self::Fruit {
218        self.map(|segment_collector| segment_collector.harvest())
219    }
220}
221
222impl<TCollector: Collector> Collector for Option<TCollector> {
223    type Fruit = Option<TCollector::Fruit>;
224
225    type Child = Option<<TCollector as Collector>::Child>;
226
227    fn for_segment(
228        &self,
229        segment_local_id: SegmentOrdinal,
230        segment: &SegmentReader,
231    ) -> crate::Result<Self::Child> {
232        Ok(if let Some(inner) = self {
233            let inner_segment_collector = inner.for_segment(segment_local_id, segment)?;
234            Some(inner_segment_collector)
235        } else {
236            None
237        })
238    }
239
240    fn requires_scoring(&self) -> bool {
241        self.as_ref()
242            .map(|inner| inner.requires_scoring())
243            .unwrap_or(false)
244    }
245
246    fn merge_fruits(
247        &self,
248        segment_fruits: Vec<<Self::Child as SegmentCollector>::Fruit>,
249    ) -> crate::Result<Self::Fruit> {
250        if let Some(inner) = self.as_ref() {
251            let inner_segment_fruits: Vec<_> = segment_fruits
252                .into_iter()
253                .flat_map(|fruit_opt| fruit_opt.into_iter())
254                .collect();
255            let fruit = inner.merge_fruits(inner_segment_fruits)?;
256            Ok(Some(fruit))
257        } else {
258            Ok(None)
259        }
260    }
261}
262
263/// The `SegmentCollector` is the trait in charge of defining the
264/// collect operation at the scale of the segment.
265///
266/// `.collect(doc, score)` will be called for every documents
267/// matching the query.
268pub trait SegmentCollector: 'static {
269    /// `Fruit` is the type for the result of our collection.
270    /// e.g. `usize` for the `Count` collector.
271    type Fruit: Fruit;
272
273    /// The query pushes the scored document to the collector via this method.
274    fn collect(&mut self, doc: DocId, score: Score);
275
276    /// The query pushes the scored document to the collector via this method.
277    /// This method is used when the collector does not require scoring.
278    ///
279    /// See [`COLLECT_BLOCK_BUFFER_LEN`](crate::COLLECT_BLOCK_BUFFER_LEN) for the
280    /// buffer size passed to the collector.
281    fn collect_block(&mut self, docs: &[DocId]) {
282        for doc in docs {
283            self.collect(*doc, 0.0);
284        }
285    }
286
287    /// Extract the fruit of the collection from the `SegmentCollector`.
288    fn harvest(self) -> Self::Fruit;
289}
290
291// -----------------------------------------------
292// Tuple implementations.
293
294impl<Left, Right> Collector for (Left, Right)
295where
296    Left: Collector,
297    Right: Collector,
298{
299    type Fruit = (Left::Fruit, Right::Fruit);
300    type Child = (Left::Child, Right::Child);
301
302    fn for_segment(
303        &self,
304        segment_local_id: u32,
305        segment: &SegmentReader,
306    ) -> crate::Result<Self::Child> {
307        let left = self.0.for_segment(segment_local_id, segment)?;
308        let right = self.1.for_segment(segment_local_id, segment)?;
309        Ok((left, right))
310    }
311
312    fn requires_scoring(&self) -> bool {
313        self.0.requires_scoring() || self.1.requires_scoring()
314    }
315
316    fn merge_fruits(
317        &self,
318        segment_fruits: Vec<<Self::Child as SegmentCollector>::Fruit>,
319    ) -> crate::Result<(Left::Fruit, Right::Fruit)> {
320        let mut left_fruits = vec![];
321        let mut right_fruits = vec![];
322        for (left_fruit, right_fruit) in segment_fruits {
323            left_fruits.push(left_fruit);
324            right_fruits.push(right_fruit);
325        }
326        Ok((
327            self.0.merge_fruits(left_fruits)?,
328            self.1.merge_fruits(right_fruits)?,
329        ))
330    }
331}
332
333impl<Left, Right> SegmentCollector for (Left, Right)
334where
335    Left: SegmentCollector,
336    Right: SegmentCollector,
337{
338    type Fruit = (Left::Fruit, Right::Fruit);
339
340    fn collect(&mut self, doc: DocId, score: Score) {
341        self.0.collect(doc, score);
342        self.1.collect(doc, score);
343    }
344
345    fn harvest(self) -> <Self as SegmentCollector>::Fruit {
346        (self.0.harvest(), self.1.harvest())
347    }
348}
349
350// 3-Tuple
351
352impl<One, Two, Three> Collector for (One, Two, Three)
353where
354    One: Collector,
355    Two: Collector,
356    Three: Collector,
357{
358    type Fruit = (One::Fruit, Two::Fruit, Three::Fruit);
359    type Child = (One::Child, Two::Child, Three::Child);
360
361    fn for_segment(
362        &self,
363        segment_local_id: u32,
364        segment: &SegmentReader,
365    ) -> crate::Result<Self::Child> {
366        let one = self.0.for_segment(segment_local_id, segment)?;
367        let two = self.1.for_segment(segment_local_id, segment)?;
368        let three = self.2.for_segment(segment_local_id, segment)?;
369        Ok((one, two, three))
370    }
371
372    fn requires_scoring(&self) -> bool {
373        self.0.requires_scoring() || self.1.requires_scoring() || self.2.requires_scoring()
374    }
375
376    fn merge_fruits(
377        &self,
378        children: Vec<<Self::Child as SegmentCollector>::Fruit>,
379    ) -> crate::Result<Self::Fruit> {
380        let mut one_fruits = vec![];
381        let mut two_fruits = vec![];
382        let mut three_fruits = vec![];
383        for (one_fruit, two_fruit, three_fruit) in children {
384            one_fruits.push(one_fruit);
385            two_fruits.push(two_fruit);
386            three_fruits.push(three_fruit);
387        }
388        Ok((
389            self.0.merge_fruits(one_fruits)?,
390            self.1.merge_fruits(two_fruits)?,
391            self.2.merge_fruits(three_fruits)?,
392        ))
393    }
394}
395
396impl<One, Two, Three> SegmentCollector for (One, Two, Three)
397where
398    One: SegmentCollector,
399    Two: SegmentCollector,
400    Three: SegmentCollector,
401{
402    type Fruit = (One::Fruit, Two::Fruit, Three::Fruit);
403
404    fn collect(&mut self, doc: DocId, score: Score) {
405        self.0.collect(doc, score);
406        self.1.collect(doc, score);
407        self.2.collect(doc, score);
408    }
409
410    fn harvest(self) -> <Self as SegmentCollector>::Fruit {
411        (self.0.harvest(), self.1.harvest(), self.2.harvest())
412    }
413}
414
415// 4-Tuple
416
417impl<One, Two, Three, Four> Collector for (One, Two, Three, Four)
418where
419    One: Collector,
420    Two: Collector,
421    Three: Collector,
422    Four: Collector,
423{
424    type Fruit = (One::Fruit, Two::Fruit, Three::Fruit, Four::Fruit);
425    type Child = (One::Child, Two::Child, Three::Child, Four::Child);
426
427    fn for_segment(
428        &self,
429        segment_local_id: u32,
430        segment: &SegmentReader,
431    ) -> crate::Result<Self::Child> {
432        let one = self.0.for_segment(segment_local_id, segment)?;
433        let two = self.1.for_segment(segment_local_id, segment)?;
434        let three = self.2.for_segment(segment_local_id, segment)?;
435        let four = self.3.for_segment(segment_local_id, segment)?;
436        Ok((one, two, three, four))
437    }
438
439    fn requires_scoring(&self) -> bool {
440        self.0.requires_scoring()
441            || self.1.requires_scoring()
442            || self.2.requires_scoring()
443            || self.3.requires_scoring()
444    }
445
446    fn merge_fruits(
447        &self,
448        children: Vec<<Self::Child as SegmentCollector>::Fruit>,
449    ) -> crate::Result<Self::Fruit> {
450        let mut one_fruits = vec![];
451        let mut two_fruits = vec![];
452        let mut three_fruits = vec![];
453        let mut four_fruits = vec![];
454        for (one_fruit, two_fruit, three_fruit, four_fruit) in children {
455            one_fruits.push(one_fruit);
456            two_fruits.push(two_fruit);
457            three_fruits.push(three_fruit);
458            four_fruits.push(four_fruit);
459        }
460        Ok((
461            self.0.merge_fruits(one_fruits)?,
462            self.1.merge_fruits(two_fruits)?,
463            self.2.merge_fruits(three_fruits)?,
464            self.3.merge_fruits(four_fruits)?,
465        ))
466    }
467}
468
469impl<One, Two, Three, Four> SegmentCollector for (One, Two, Three, Four)
470where
471    One: SegmentCollector,
472    Two: SegmentCollector,
473    Three: SegmentCollector,
474    Four: SegmentCollector,
475{
476    type Fruit = (One::Fruit, Two::Fruit, Three::Fruit, Four::Fruit);
477
478    fn collect(&mut self, doc: DocId, score: Score) {
479        self.0.collect(doc, score);
480        self.1.collect(doc, score);
481        self.2.collect(doc, score);
482        self.3.collect(doc, score);
483    }
484
485    fn harvest(self) -> <Self as SegmentCollector>::Fruit {
486        (
487            self.0.harvest(),
488            self.1.harvest(),
489            self.2.harvest(),
490            self.3.harvest(),
491        )
492    }
493}
494
495impl_downcast!(Fruit);
496
497#[cfg(test)]
498pub(crate) mod tests;