1use crate::dsl::Field;
15use crate::segment::SegmentReader;
16use crate::structures::TERMINATED;
17use crate::structures::fast_field::{
18 FAST_FIELD_MISSING, FastFieldColumnType, SingleValueCursor, f64_to_sortable_u64, zigzag_decode,
19};
20use crate::{DocId, Score};
21
22use super::docset::DocSet;
23use super::traits::{CountFuture, Query, Scorer, ScorerFuture};
24
25#[derive(Debug, Clone)]
29pub enum RangeBound {
30 U64 { min: Option<u64>, max: Option<u64> },
32 I64 { min: Option<i64>, max: Option<i64> },
34 F64 { min: Option<f64>, max: Option<f64> },
36}
37
38#[derive(Clone, Copy, Debug, PartialEq)]
40enum CompiledRange {
41 Raw { lo: u64, hi: u64 },
42 Signed { lo: i64, hi: i64 },
43}
44
45impl CompiledRange {
46 fn may_match(self, bounds: Option<(u64, u64)>) -> bool {
47 let Some((min, max)) = bounds else {
48 return true;
49 };
50 if min == max {
51 return self.contains(min);
52 }
53 match self {
54 Self::Raw { lo, hi } => lo <= hi && lo <= max.min(FAST_FIELD_MISSING - 1) && hi >= min,
55 Self::Signed { .. } => true,
58 }
59 }
60
61 #[inline]
62 fn contains(self, raw: u64) -> bool {
63 if raw == FAST_FIELD_MISSING {
64 return false;
65 }
66 match self {
67 Self::Raw { lo, hi } => raw >= lo && raw <= hi,
68 Self::Signed { lo, hi } => {
69 let value = zigzag_decode(raw);
70 value >= lo && value <= hi
71 }
72 }
73 }
74}
75
76impl RangeBound {
77 fn compile(&self) -> CompiledRange {
78 match *self {
79 Self::U64 { min, max } => CompiledRange::Raw {
80 lo: min.unwrap_or(0),
81 hi: max.unwrap_or(u64::MAX - 1),
82 },
83 Self::I64 { min, max } => CompiledRange::Signed {
84 lo: min.unwrap_or(i64::MIN),
85 hi: max.unwrap_or(i64::MAX),
86 },
87 Self::F64 { min, max } => CompiledRange::Raw {
88 lo: min.map(f64_to_sortable_u64).unwrap_or(0),
89 hi: max.map(f64_to_sortable_u64).unwrap_or(u64::MAX - 1),
90 },
91 }
92 }
93}
94
95#[derive(Debug, Clone)]
102pub struct RangeQuery {
103 pub field: Field,
104 pub bound: RangeBound,
105}
106
107impl std::fmt::Display for RangeQuery {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 match &self.bound {
110 RangeBound::U64 { min, max } => write!(
111 f,
112 "Range({}:[{} TO {}])",
113 self.field.0,
114 min.map_or("*".to_string(), |v| v.to_string()),
115 max.map_or("*".to_string(), |v| v.to_string()),
116 ),
117 RangeBound::I64 { min, max } => write!(
118 f,
119 "Range({}:[{} TO {}])",
120 self.field.0,
121 min.map_or("*".to_string(), |v| v.to_string()),
122 max.map_or("*".to_string(), |v| v.to_string()),
123 ),
124 RangeBound::F64 { min, max } => write!(
125 f,
126 "Range({}:[{} TO {}])",
127 self.field.0,
128 min.map_or("*".to_string(), |v| v.to_string()),
129 max.map_or("*".to_string(), |v| v.to_string()),
130 ),
131 }
132 }
133}
134
135impl RangeQuery {
136 pub fn new(field: Field, bound: RangeBound) -> Self {
137 Self { field, bound }
138 }
139
140 pub fn u64(field: Field, min: Option<u64>, max: Option<u64>) -> Self {
142 Self::new(field, RangeBound::U64 { min, max })
143 }
144
145 pub fn i64(field: Field, min: Option<i64>, max: Option<i64>) -> Self {
147 Self::new(field, RangeBound::I64 { min, max })
148 }
149
150 pub fn f64(field: Field, min: Option<f64>, max: Option<f64>) -> Self {
152 Self::new(field, RangeBound::F64 { min, max })
153 }
154}
155
156impl Query for RangeQuery {
157 fn scorer<'a>(&self, reader: &'a SegmentReader, _limit: usize) -> ScorerFuture<'a> {
158 let field = self.field;
159 let bound = self.bound.clone();
160 Box::pin(async move {
161 match RangeScorer::new(reader, field, &bound) {
162 Ok(scorer) => Ok(Box::new(scorer) as Box<dyn Scorer>),
163 Err(_) => Ok(Box::new(EmptyRangeScorer) as Box<dyn Scorer>),
164 }
165 })
166 }
167
168 #[cfg(feature = "sync")]
169 fn scorer_sync<'a>(
170 &self,
171 reader: &'a SegmentReader,
172 _limit: usize,
173 ) -> crate::Result<Box<dyn Scorer + 'a>> {
174 match RangeScorer::new(reader, self.field, &self.bound) {
175 Ok(scorer) => Ok(Box::new(scorer) as Box<dyn Scorer + 'a>),
176 Err(_) => Ok(Box::new(EmptyRangeScorer) as Box<dyn Scorer + 'a>),
177 }
178 }
179
180 fn count_estimate<'a>(&self, reader: &'a SegmentReader) -> CountFuture<'a> {
181 let num_docs = reader.num_docs();
182 Box::pin(async move { Ok(num_docs / 2) })
184 }
185
186 fn is_filter(&self) -> bool {
187 true
188 }
189
190 fn as_doc_predicate<'a>(&self, reader: &'a SegmentReader) -> Option<super::DocPredicate<'a>> {
191 let fast_field = reader.fast_field(self.field.0)?;
192 let bound = self.bound.compile();
193 Some(Box::new(move |doc_id| {
194 bound.contains(fast_field.get_u64(doc_id))
195 }))
196 }
197
198 fn as_doc_bitset(&self, reader: &SegmentReader) -> Option<super::DocBitset> {
199 let fast_field = reader.fast_field(self.field.0)?;
200 if fast_field.multi {
201 let pred = self.as_doc_predicate(reader)?;
204 return Some(super::DocBitset::from_predicate(reader.num_docs(), &*pred));
205 }
206 let bound = self.bound.compile();
207 let mut bits = super::DocBitset::new(reader.num_docs());
208 let _: Result<(), std::convert::Infallible> = fast_field
211 .try_scan_single_value_batches_where(
212 |block| {
213 fast_field.column_type == FastFieldColumnType::TextOrdinal
214 || bound.may_match(block.value_bounds())
215 },
216 |start, values| {
217 match bound {
218 CompiledRange::Raw { lo, hi } => {
219 bits.insert_matching_values(start, values, |raw| {
220 raw != FAST_FIELD_MISSING && raw >= lo && raw <= hi
221 });
222 }
223 CompiledRange::Signed { lo, hi } => {
224 bits.insert_matching_values(start, values, |raw| {
225 let value = zigzag_decode(raw);
226 raw != FAST_FIELD_MISSING && value >= lo && value <= hi
227 });
228 }
229 }
230 Ok(())
231 },
232 );
233 Some(bits)
234 }
235
236 fn bitset_cardinality_estimate(&self, reader: &SegmentReader) -> Option<u64> {
237 let pred = self.as_doc_predicate(reader)?;
242 let n = reader.num_docs();
243 if n == 0 {
244 return Some(0);
245 }
246 const SAMPLES: u32 = 1024;
247 if n <= SAMPLES {
248 return Some((0..n).filter(|&d| pred(d)).count() as u64);
249 }
250 let step = n / SAMPLES;
251 let hits = (0..SAMPLES).filter(|&i| pred(i * step)).count() as u64;
252 Some(((hits * n as u64) / SAMPLES as u64).max(1))
253 }
254}
255
256struct RangeScorer<'a> {
264 fast_field: &'a crate::structures::fast_field::FastFieldReader,
266 bound: CompiledRange,
267 current: u32,
269 num_docs: u32,
270 cursor: Option<SingleValueCursor<'a>>,
271 batch_start: u32,
273 batch_end: u32,
274 matches: u64,
275 scalar_probes: u8,
276}
277
278struct EmptyRangeScorer;
280
281impl<'a> RangeScorer<'a> {
282 fn new(
283 reader: &'a SegmentReader,
284 field: Field,
285 bound: &RangeBound,
286 ) -> Result<Self, EmptyRangeScorer> {
287 let fast_field = reader.fast_field(field.0).ok_or(EmptyRangeScorer)?;
288 let num_docs = reader.num_docs();
289 let mut scorer = Self {
290 fast_field,
291 bound: bound.compile(),
292 current: 0,
293 num_docs,
294 cursor: (!fast_field.multi).then(|| SingleValueCursor::new(fast_field)),
295 batch_start: 0,
296 batch_end: 0,
297 matches: 0,
298 scalar_probes: 0,
299 };
300
301 scorer.scan_from(0);
302 Ok(scorer)
303 }
304
305 #[inline]
307 fn scan_from(&mut self, mut next: DocId) {
308 while next < self.num_docs {
309 if next < self.batch_end {
310 let offset = next - self.batch_start;
311 let remaining = self.matches & (u64::MAX << offset);
312 if remaining != 0 {
313 self.current = self.batch_start + remaining.trailing_zeros();
314 return;
315 }
316 next = self.batch_end;
317 continue;
318 }
319 if self.scalar_probes < 8 || self.cursor.is_none() {
320 self.scalar_probes = self.scalar_probes.saturating_add(1);
321 if self.bound.contains(self.fast_field.get_u64(next)) {
322 self.current = next;
323 return;
324 }
325 next += 1;
326 continue;
327 }
328 if !self.refill_batch(next) {
329 break;
330 }
331 }
332 self.current = self.num_docs;
333 }
334 #[inline(never)]
336 fn refill_batch(&mut self, start: DocId) -> bool {
337 let mut values = [0u64; 64];
338 let count = self.cursor.as_mut().unwrap().read_batch(start, &mut values);
339 if count == 0 {
341 return false;
342 }
343 self.batch_start = start;
344 self.batch_end = start + count as u32;
345 self.matches = values[..count]
346 .iter()
347 .enumerate()
348 .fold(0, |mask, (i, &raw)| {
349 mask | (u64::from(self.bound.contains(raw)) << i)
350 });
351 true
352 }
353}
354
355impl DocSet for RangeScorer<'_> {
356 fn doc(&self) -> DocId {
357 if self.current >= self.num_docs {
358 TERMINATED
359 } else {
360 self.current
361 }
362 }
363
364 fn advance(&mut self) -> DocId {
365 if self.current < self.num_docs {
366 self.scan_from(self.current + 1);
367 }
368 self.doc()
369 }
370
371 fn seek(&mut self, target: DocId) -> DocId {
372 if self.current >= self.num_docs {
373 return TERMINATED;
374 }
375 if target <= self.current {
376 return self.current;
377 }
378 if target >= self.batch_end && target > self.current + 1 {
379 self.scalar_probes = 0;
380 }
381 self.scan_from(target);
382 self.doc()
383 }
384
385 fn size_hint(&self) -> u32 {
386 self.num_docs.saturating_sub(self.current)
388 }
389}
390
391impl Scorer for RangeScorer<'_> {
392 fn score(&self) -> Score {
393 1.0
394 }
395}
396
397impl DocSet for EmptyRangeScorer {
398 fn doc(&self) -> DocId {
399 TERMINATED
400 }
401 fn advance(&mut self) -> DocId {
402 TERMINATED
403 }
404 fn seek(&mut self, _target: DocId) -> DocId {
405 TERMINATED
406 }
407 fn size_hint(&self) -> u32 {
408 0
409 }
410}
411
412impl Scorer for EmptyRangeScorer {
413 fn score(&self) -> Score {
414 0.0
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[test]
423 fn test_range_bound_u64_compile() {
424 let b = RangeBound::U64 {
425 min: Some(10),
426 max: Some(100),
427 };
428 assert_eq!(b.compile(), CompiledRange::Raw { lo: 10, hi: 100 });
429 }
430
431 #[test]
432 fn test_range_bound_f64_compile_preserves_order() {
433 let b1 = RangeBound::F64 {
434 min: Some(-1.0),
435 max: Some(1.0),
436 };
437 let CompiledRange::Raw { lo, hi } = b1.compile() else {
438 panic!("expected raw bounds")
439 };
440 assert!(lo < hi);
441
442 let b2 = RangeBound::F64 {
443 min: Some(0.0),
444 max: Some(100.0),
445 };
446 let CompiledRange::Raw { lo, hi } = b2.compile() else {
447 panic!("expected raw bounds")
448 };
449 assert!(lo < hi);
450 }
451
452 #[test]
453 fn test_range_bound_open_bounds() {
454 let b = RangeBound::U64 {
455 min: None,
456 max: None,
457 };
458 assert_eq!(
459 b.compile(),
460 CompiledRange::Raw {
461 lo: 0,
462 hi: u64::MAX - 1
463 }
464 );
465 }
466
467 #[test]
468 fn test_range_query_constructors() {
469 let q = RangeQuery::u64(Field(0), Some(10), Some(100));
470 assert_eq!(q.field, Field(0));
471 assert!(matches!(
472 q.bound,
473 RangeBound::U64 {
474 min: Some(10),
475 max: Some(100)
476 }
477 ));
478
479 let q = RangeQuery::i64(Field(1), Some(-50), Some(50));
480 assert!(matches!(
481 q.bound,
482 RangeBound::I64 {
483 min: Some(-50),
484 max: Some(50)
485 }
486 ));
487
488 let q = RangeQuery::f64(Field(2), Some(0.5), Some(9.5));
489 assert!(matches!(q.bound, RangeBound::F64 { .. }));
490 }
491}