Skip to main content

luci/query/
prefix.rs

1//! PrefixQuery: match documents containing terms with a given prefix.
2//!
3//! Per-segment rewrite: enumerates matching terms in `scorer_supplier(reader)`,
4//! routes through [`ConstantScoreMultiTermSupplier`] (the shared
5//! `FilterScorer` + `BufferedUnionScorer` primitive used by all
6//! constant-score multi-term queries — prefix, wildcard, fuzzy, regexp).
7//!
8//! See [[optimization-multiterm-constant-score-rewrite]],
9//! [[fix-disjunction-heap-inefficiency]], and
10//! [[query-dsl#Term-Level Queries]].
11
12use crate::core::{Result, ScoreMode};
13
14use crate::query::multi_term::ConstantScoreMultiTermSupplier;
15use crate::query::{BoundQuery, Query, ScorerSupplier};
16use crate::search::searcher::Searcher;
17use crate::segment::reader::SegmentReader;
18
19pub struct PrefixQuery {
20    pub field: String,
21    pub value: String,
22}
23
24impl Query for PrefixQuery {
25    fn bind(&self, _searcher: &Searcher, _score_mode: ScoreMode) -> Result<Box<dyn BoundQuery>> {
26        Ok(Box::new(BoundPrefixQuery {
27            field: self.field.clone(),
28            prefix: self.value.clone(),
29        }))
30    }
31}
32
33struct BoundPrefixQuery {
34    field: String,
35    prefix: String,
36}
37
38impl BoundQuery for BoundPrefixQuery {
39    fn scorer_supplier(&self, reader: &SegmentReader) -> Result<Option<Box<dyn ScorerSupplier>>> {
40        let field_id = match reader
41            .header()
42            .fields
43            .iter()
44            .find(|f| f.field_name == self.field)
45            .map(|f| f.field_id)
46        {
47            Some(id) => id,
48            None => return Ok(None),
49        };
50
51        // Enumerate matching prefix terms in this segment.
52        let terms = reader.terms_with_prefix(field_id, &self.prefix);
53        if terms.is_empty() {
54            return Ok(None);
55        }
56
57        Ok(Some(Box::new(ConstantScoreMultiTermSupplier::new(
58            reader, field_id, terms,
59        ))))
60    }
61}