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}