Skip to main content

kevy_text/
segment_opts.rs

1//! What a query asks for, beyond its text and result limit — the clause
2//! options and the shapes they answer in. A child module of `segment`
3//! (declared via `#[path]`), re-exported from it, so the crate's public
4//! surface is unchanged by the split.
5
6use super::{CorpusStats, Filter, TextMatch};
7
8/// An order to select the top hits by, other than the score.
9///
10/// The key function maps a stored value's raw bytes to an
11/// order-preserving encoding, computed once per candidate; the segment
12/// then compares bytes and never learns what a number is. `None` from it
13/// means the document has no usable value for the field, which sorts
14/// **last in both directions** — missing is not a value, and placing it
15/// at one end or the other by direction would make "the oldest" and "the
16/// newest" disagree about where the unknowns went.
17#[derive(Clone, Copy)]
18pub struct Sort<'a> {
19    /// Which declared value field orders the result.
20    pub field: usize,
21    /// Descending when true.
22    pub desc: bool,
23    /// The order-preserving encoding of one stored value.
24    pub key: &'a dyn Fn(&[u8]) -> Option<Vec<u8>>,
25}
26
27/// Collapse the page so only the best document per value of a stored
28/// field appears.
29///
30/// The key is the value's *identity*, coerced — so `1` and `1.0` in a
31/// field declared `f64` are one value rather than two. A document with no
32/// value for the field is its own group: `DISTINCT` removes documents
33/// shown to share a value, and one that has none has not been shown to
34/// share anything.
35#[derive(Clone, Copy)]
36pub struct Distinct<'a> {
37    /// Which declared value field identifies a group.
38    pub field: usize,
39    /// The identity of one stored value.
40    pub key: &'a dyn Fn(&[u8]) -> Option<Vec<u8>>,
41}
42
43/// Count the values of a stored field over the whole match set.
44///
45/// Buckets are keyed by the value's *identity* — the same coerced key
46/// `DISTINCT` groups by, so `1` and `1.0` in a field declared `f64` are
47/// one bucket — while the reported label is a spelling that really occurs
48/// in the corpus rather than a re-serialisation.
49#[derive(Clone, Copy)]
50pub struct Facet<'a> {
51    /// Which declared value field to count.
52    pub field: usize,
53    /// The identity of one stored value.
54    pub key: &'a dyn Fn(&[u8]) -> Option<Vec<u8>>,
55}
56
57/// One value bucket: the identity a cross-shard merge sums by, a spelling
58/// of it that occurs in the corpus, and how many documents matched with
59/// it.
60pub type Bucket = (Vec<u8>, Vec<u8>, u64);
61
62/// One faceted query's answer: the page, and a count per value for each
63/// requested field.
64#[derive(Debug)]
65pub struct FacetedMatches {
66    /// The ranked page, exactly what an unfaceted query would return.
67    pub hits: Vec<TextMatch>,
68    /// Per requested facet field, `(identity, label, count)` over the
69    /// whole match set. The identity is what a cross-shard merge sums by;
70    /// the label is what it reports.
71    pub facets: Vec<Vec<Bucket>>,
72}
73
74/// Everything a MATCH query carries beyond its text and result limit.
75///
76/// Grouping them keeps the query entry point from growing a parameter per
77/// clause, and gives every clause one place to be defaulted from
78/// ([`QueryOpts::default`] is the plain, exact, unscoped query).
79#[derive(Debug, Clone, Copy, Default)]
80pub struct QueryOpts<'a> {
81    /// Corpus-wide BM25 statistics — the second pass of a cross-shard
82    /// query. `None` scores against this segment's own slice.
83    pub stats: Option<&'a CorpusStats>,
84    /// Edit distance allowed on bare terms (`TYPO n`); 0 = exact.
85    pub typo: u32,
86    /// Field positions the query is restricted to (`IN <field…>`); empty
87    /// = every field.
88    pub fields: &'a [usize],
89    /// `FILTER`: non-scoring predicates, ANDed. Applied before the top-K
90    /// — filtering afterwards would return fewer hits than exist.
91    pub filter: &'a [Filter<'a>],
92    /// `SORT`: select by a stored value instead of by score. Selecting,
93    /// not re-ordering: a document that wins on the sort key must be
94    /// chosen even when its score would never have reached the page.
95    pub sort: Option<Sort<'a>>,
96    /// `DISTINCT`: at most one hit per value of a stored field. Applied
97    /// during selection, so the page is filled with `limit` DISTINCT
98    /// documents rather than `limit` documents that then collapse.
99    pub distinct: Option<Distinct<'a>>,
100}
101
102impl core::fmt::Debug for Sort<'_> {
103    /// Prints every field except `key`.
104    ///
105    /// The ordering key is a `&dyn Fn`, which has no `Debug` and no stable
106    /// identity worth printing — it shows as `<fn>` so the rest of the
107    /// struct stays inspectable.
108    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
109        f.debug_struct("Sort")
110            .field("field", &self.field)
111            .field("desc", &self.desc)
112            .field("key", &"<fn>")
113            .finish()
114    }
115}
116
117impl core::fmt::Debug for Distinct<'_> {
118    /// Prints every field except `key`.
119    ///
120    /// The identity key is a `&dyn Fn`, which has no `Debug` and no stable
121    /// identity worth printing — it shows as `<fn>` so the rest of the
122    /// struct stays inspectable.
123    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
124        f.debug_struct("Distinct").field("field", &self.field).field("key", &"<fn>").finish()
125    }
126}
127
128impl core::fmt::Debug for Facet<'_> {
129    /// Prints every field except `key`.
130    ///
131    /// The bucketing key is a `&dyn Fn`, which has no `Debug` and no stable
132    /// identity worth printing — it shows as `<fn>` so the rest of the
133    /// struct stays inspectable.
134    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135        f.debug_struct("Facet").field("field", &self.field).field("key", &"<fn>").finish()
136    }
137}