1use crate::dsl::Field;
15use crate::segment::SegmentReader;
16use crate::structures::TERMINATED;
17use crate::structures::fast_field::{FAST_FIELD_MISSING, f64_to_sortable_u64, zigzag_decode};
18use crate::{DocId, Score};
19
20use super::docset::DocSet;
21use super::traits::{CountFuture, Query, Scorer, ScorerFuture};
22
23#[derive(Debug, Clone)]
27pub enum RangeBound {
28 U64 { min: Option<u64>, max: Option<u64> },
30 I64 { min: Option<i64>, max: Option<i64> },
32 F64 { min: Option<f64>, max: Option<f64> },
34}
35
36#[derive(Clone, Copy, Debug, PartialEq)]
38enum CompiledRange {
39 Raw { lo: u64, hi: u64 },
40 Signed { lo: i64, hi: i64 },
41}
42
43impl CompiledRange {
44 #[inline]
45 fn contains(self, raw: u64) -> bool {
46 if raw == FAST_FIELD_MISSING {
47 return false;
48 }
49 match self {
50 Self::Raw { lo, hi } => raw >= lo && raw <= hi,
51 Self::Signed { lo, hi } => {
52 let value = zigzag_decode(raw);
53 value >= lo && value <= hi
54 }
55 }
56 }
57}
58
59impl RangeBound {
60 fn compile(&self) -> CompiledRange {
61 match *self {
62 Self::U64 { min, max } => CompiledRange::Raw {
63 lo: min.unwrap_or(0),
64 hi: max.unwrap_or(u64::MAX - 1),
65 },
66 Self::I64 { min, max } => CompiledRange::Signed {
67 lo: min.unwrap_or(i64::MIN),
68 hi: max.unwrap_or(i64::MAX),
69 },
70 Self::F64 { min, max } => CompiledRange::Raw {
71 lo: min.map(f64_to_sortable_u64).unwrap_or(0),
72 hi: max.map(f64_to_sortable_u64).unwrap_or(u64::MAX - 1),
73 },
74 }
75 }
76}
77
78#[derive(Debug, Clone)]
85pub struct RangeQuery {
86 pub field: Field,
87 pub bound: RangeBound,
88}
89
90impl std::fmt::Display for RangeQuery {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 match &self.bound {
93 RangeBound::U64 { min, max } => write!(
94 f,
95 "Range({}:[{} TO {}])",
96 self.field.0,
97 min.map_or("*".to_string(), |v| v.to_string()),
98 max.map_or("*".to_string(), |v| v.to_string()),
99 ),
100 RangeBound::I64 { min, max } => write!(
101 f,
102 "Range({}:[{} TO {}])",
103 self.field.0,
104 min.map_or("*".to_string(), |v| v.to_string()),
105 max.map_or("*".to_string(), |v| v.to_string()),
106 ),
107 RangeBound::F64 { min, max } => write!(
108 f,
109 "Range({}:[{} TO {}])",
110 self.field.0,
111 min.map_or("*".to_string(), |v| v.to_string()),
112 max.map_or("*".to_string(), |v| v.to_string()),
113 ),
114 }
115 }
116}
117
118impl RangeQuery {
119 pub fn new(field: Field, bound: RangeBound) -> Self {
120 Self { field, bound }
121 }
122
123 pub fn u64(field: Field, min: Option<u64>, max: Option<u64>) -> Self {
125 Self::new(field, RangeBound::U64 { min, max })
126 }
127
128 pub fn i64(field: Field, min: Option<i64>, max: Option<i64>) -> Self {
130 Self::new(field, RangeBound::I64 { min, max })
131 }
132
133 pub fn f64(field: Field, min: Option<f64>, max: Option<f64>) -> Self {
135 Self::new(field, RangeBound::F64 { min, max })
136 }
137}
138
139impl Query for RangeQuery {
140 fn scorer<'a>(&self, reader: &'a SegmentReader, _limit: usize) -> ScorerFuture<'a> {
141 let field = self.field;
142 let bound = self.bound.clone();
143 Box::pin(async move {
144 match RangeScorer::new(reader, field, &bound) {
145 Ok(scorer) => Ok(Box::new(scorer) as Box<dyn Scorer>),
146 Err(_) => Ok(Box::new(EmptyRangeScorer) as Box<dyn Scorer>),
147 }
148 })
149 }
150
151 #[cfg(feature = "sync")]
152 fn scorer_sync<'a>(
153 &self,
154 reader: &'a SegmentReader,
155 _limit: usize,
156 ) -> crate::Result<Box<dyn Scorer + 'a>> {
157 match RangeScorer::new(reader, self.field, &self.bound) {
158 Ok(scorer) => Ok(Box::new(scorer) as Box<dyn Scorer + 'a>),
159 Err(_) => Ok(Box::new(EmptyRangeScorer) as Box<dyn Scorer + 'a>),
160 }
161 }
162
163 fn count_estimate<'a>(&self, reader: &'a SegmentReader) -> CountFuture<'a> {
164 let num_docs = reader.num_docs();
165 Box::pin(async move { Ok(num_docs / 2) })
167 }
168
169 fn is_filter(&self) -> bool {
170 true
171 }
172
173 fn as_doc_predicate<'a>(&self, reader: &'a SegmentReader) -> Option<super::DocPredicate<'a>> {
174 let fast_field = reader.fast_field(self.field.0)?;
175 let bound = self.bound.compile();
176 Some(Box::new(move |doc_id| {
177 bound.contains(fast_field.get_u64(doc_id))
178 }))
179 }
180
181 fn as_doc_bitset(&self, reader: &SegmentReader) -> Option<super::DocBitset> {
182 let fast_field = reader.fast_field(self.field.0)?;
183 if fast_field.multi {
184 let pred = self.as_doc_predicate(reader)?;
187 return Some(super::DocBitset::from_predicate(reader.num_docs(), &*pred));
188 }
189 let bound = self.bound.compile();
190 let mut bits = super::DocBitset::new(reader.num_docs());
191 fast_field.scan_single_values(|doc_id, raw| {
194 if bound.contains(raw) {
195 bits.set(doc_id);
196 }
197 });
198 Some(bits)
199 }
200
201 fn bitset_cardinality_estimate(&self, reader: &SegmentReader) -> Option<u64> {
202 let pred = self.as_doc_predicate(reader)?;
207 let n = reader.num_docs();
208 if n == 0 {
209 return Some(0);
210 }
211 const SAMPLES: u32 = 1024;
212 if n <= SAMPLES {
213 return Some((0..n).filter(|&d| pred(d)).count() as u64);
214 }
215 let step = n / SAMPLES;
216 let hits = (0..SAMPLES).filter(|&i| pred(i * step)).count() as u64;
217 Some(((hits * n as u64) / SAMPLES as u64).max(1))
218 }
219}
220
221struct RangeScorer<'a> {
229 fast_field: &'a crate::structures::fast_field::FastFieldReader,
231 bound: CompiledRange,
232 current: u32,
234 num_docs: u32,
235}
236
237struct EmptyRangeScorer;
239
240impl<'a> RangeScorer<'a> {
241 fn new(
242 reader: &'a SegmentReader,
243 field: Field,
244 bound: &RangeBound,
245 ) -> Result<Self, EmptyRangeScorer> {
246 let fast_field = reader.fast_field(field.0).ok_or(EmptyRangeScorer)?;
247 let num_docs = reader.num_docs();
248 let mut scorer = Self {
249 fast_field,
250 bound: bound.compile(),
251 current: 0,
252 num_docs,
253 };
254
255 if num_docs > 0 && !scorer.matches(0) {
257 scorer.scan_forward();
258 }
259 Ok(scorer)
260 }
261
262 #[inline]
263 fn matches(&self, doc_id: DocId) -> bool {
264 self.bound.contains(self.fast_field.get_u64(doc_id))
265 }
266
267 fn scan_forward(&mut self) {
269 loop {
270 self.current += 1;
271 if self.current >= self.num_docs {
272 self.current = self.num_docs;
273 return;
274 }
275 if self.matches(self.current) {
276 return;
277 }
278 }
279 }
280}
281
282impl DocSet for RangeScorer<'_> {
283 fn doc(&self) -> DocId {
284 if self.current >= self.num_docs {
285 TERMINATED
286 } else {
287 self.current
288 }
289 }
290
291 fn advance(&mut self) -> DocId {
292 self.scan_forward();
293 self.doc()
294 }
295
296 fn seek(&mut self, target: DocId) -> DocId {
297 if self.current >= self.num_docs {
298 return TERMINATED;
299 }
300 if target <= self.current {
301 return self.current;
302 }
303 self.current = target - 1;
305 self.scan_forward();
306 self.doc()
307 }
308
309 fn size_hint(&self) -> u32 {
310 self.num_docs.saturating_sub(self.current)
312 }
313}
314
315impl Scorer for RangeScorer<'_> {
316 fn score(&self) -> Score {
317 1.0
318 }
319}
320
321impl DocSet for EmptyRangeScorer {
322 fn doc(&self) -> DocId {
323 TERMINATED
324 }
325 fn advance(&mut self) -> DocId {
326 TERMINATED
327 }
328 fn seek(&mut self, _target: DocId) -> DocId {
329 TERMINATED
330 }
331 fn size_hint(&self) -> u32 {
332 0
333 }
334}
335
336impl Scorer for EmptyRangeScorer {
337 fn score(&self) -> Score {
338 0.0
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn test_range_bound_u64_compile() {
348 let b = RangeBound::U64 {
349 min: Some(10),
350 max: Some(100),
351 };
352 assert_eq!(b.compile(), CompiledRange::Raw { lo: 10, hi: 100 });
353 }
354
355 #[test]
356 fn test_range_bound_f64_compile_preserves_order() {
357 let b1 = RangeBound::F64 {
358 min: Some(-1.0),
359 max: Some(1.0),
360 };
361 let CompiledRange::Raw { lo, hi } = b1.compile() else {
362 panic!("expected raw bounds")
363 };
364 assert!(lo < hi);
365
366 let b2 = RangeBound::F64 {
367 min: Some(0.0),
368 max: Some(100.0),
369 };
370 let CompiledRange::Raw { lo, hi } = b2.compile() else {
371 panic!("expected raw bounds")
372 };
373 assert!(lo < hi);
374 }
375
376 #[test]
377 fn test_range_bound_open_bounds() {
378 let b = RangeBound::U64 {
379 min: None,
380 max: None,
381 };
382 assert_eq!(
383 b.compile(),
384 CompiledRange::Raw {
385 lo: 0,
386 hi: u64::MAX - 1
387 }
388 );
389 }
390
391 #[test]
392 fn test_range_query_constructors() {
393 let q = RangeQuery::u64(Field(0), Some(10), Some(100));
394 assert_eq!(q.field, Field(0));
395 assert!(matches!(
396 q.bound,
397 RangeBound::U64 {
398 min: Some(10),
399 max: Some(100)
400 }
401 ));
402
403 let q = RangeQuery::i64(Field(1), Some(-50), Some(50));
404 assert!(matches!(
405 q.bound,
406 RangeBound::I64 {
407 min: Some(-50),
408 max: Some(50)
409 }
410 ));
411
412 let q = RangeQuery::f64(Field(2), Some(0.5), Some(9.5));
413 assert!(matches!(q.bound, RangeBound::F64 { .. }));
414 }
415}