Skip to main content

lb_tantivy/query/range_query/
range_query_fastfield.rs

1//! Fastfields support efficient scanning for range queries.
2//! We use this variant only if the fastfield exists, otherwise the default in `range_query` is
3//! used, which uses the term dictionary + postings.
4
5use std::net::Ipv6Addr;
6use std::ops::{Bound, RangeInclusive};
7
8use columnar::{
9    Column, ColumnType, MonotonicallyMappableToU128, MonotonicallyMappableToU64, NumericalType,
10    StrColumn,
11};
12use common::bounds::{BoundsRange, TransformBound};
13
14use super::fast_field_range_doc_set::RangeDocSet;
15use crate::query::{
16    AllScorer, ConstScorer, EmptyScorer, EnableScoring, Explanation, Query, Scorer, Weight,
17};
18use crate::schema::{Type, ValueBytes};
19use crate::{DocId, DocSet, Score, SegmentReader, TantivyError, Term};
20
21#[derive(Clone, Debug)]
22/// `FastFieldRangeQuery` is the same as [RangeQuery] but only uses the fast field
23pub struct FastFieldRangeQuery {
24    bounds: BoundsRange<Term>,
25}
26impl FastFieldRangeQuery {
27    /// Create new `FastFieldRangeQuery`
28    pub fn new(lower_bound: Bound<Term>, upper_bound: Bound<Term>) -> FastFieldRangeQuery {
29        Self {
30            bounds: BoundsRange::new(lower_bound, upper_bound),
31        }
32    }
33}
34
35impl Query for FastFieldRangeQuery {
36    fn weight(&self, _enable_scoring: EnableScoring<'_>) -> crate::Result<Box<dyn Weight>> {
37        Ok(Box::new(FastFieldRangeWeight::new(self.bounds.clone())))
38    }
39}
40
41/// `FastFieldRangeWeight` uses the fast field to execute range queries.
42#[derive(Clone, Debug)]
43pub struct FastFieldRangeWeight {
44    bounds: BoundsRange<Term>,
45}
46
47impl FastFieldRangeWeight {
48    /// Create a new FastFieldRangeWeight
49    pub fn new(bounds: BoundsRange<Term>) -> Self {
50        Self { bounds }
51    }
52}
53
54impl Weight for FastFieldRangeWeight {
55    fn scorer(&self, reader: &SegmentReader, boost: Score) -> crate::Result<Box<dyn Scorer>> {
56        // Check if both bounds are Bound::Unbounded
57        if self.bounds.is_unbounded() {
58            return Ok(Box::new(AllScorer::new(reader.max_doc())));
59        }
60
61        let term = self
62            .bounds
63            .get_inner()
64            .expect("At least one bound must be set");
65        let schema = reader.schema();
66        let field_type = schema.get_field_entry(term.field()).field_type();
67        assert_eq!(
68            term.typ(),
69            field_type.value_type(),
70            "Field is of type {:?}, but got term of type {:?}",
71            field_type,
72            term.typ()
73        );
74        let field_name = term.get_full_path(reader.schema());
75
76        let get_value_bytes = |term: &Term| term.value().value_bytes_payload();
77
78        let term_value = term.value();
79        if field_type.is_json() {
80            let bounds = self
81                .bounds
82                .map_bound(|term| term.value().as_json_value_bytes().unwrap().to_owned());
83            // Unlike with other field types JSON may have multiple columns of different types
84            // under the same name
85            //
86            // In the JSON case the provided type in term may not exactly match the column type,
87            // especially with the numeric type interpolation
88            let json_value_bytes = term_value
89                .as_json_value_bytes()
90                .expect("expected json type in term");
91            let typ = json_value_bytes.typ();
92
93            match typ {
94                Type::Str => {
95                    let Some(str_dict_column): Option<StrColumn> =
96                        reader.fast_fields().str(&field_name)?
97                    else {
98                        return Ok(Box::new(EmptyScorer));
99                    };
100                    let dict = str_dict_column.dictionary();
101
102                    let bounds = self.bounds.map_bound(get_value_bytes);
103                    // Get term ids for terms
104                    let (lower_bound, upper_bound) =
105                        dict.term_bounds_to_ord(bounds.lower_bound, bounds.upper_bound)?;
106                    let fast_field_reader = reader.fast_fields();
107                    let Some((column, _col_type)) = fast_field_reader
108                        .u64_lenient_for_type(Some(&[ColumnType::Str]), &field_name)?
109                    else {
110                        return Ok(Box::new(EmptyScorer));
111                    };
112                    search_on_u64_ff(column, boost, BoundsRange::new(lower_bound, upper_bound))
113                }
114                Type::U64 | Type::I64 | Type::F64 => {
115                    search_on_json_numerical_field(reader, &field_name, typ, bounds, boost)
116                }
117                Type::Date => {
118                    let fast_field_reader = reader.fast_fields();
119                    let Some((column, _col_type)) = fast_field_reader
120                        .u64_lenient_for_type(Some(&[ColumnType::DateTime]), &field_name)?
121                    else {
122                        return Ok(Box::new(EmptyScorer));
123                    };
124                    let bounds = bounds.map_bound(|term| term.as_date().unwrap().to_u64());
125                    search_on_u64_ff(
126                        column,
127                        boost,
128                        BoundsRange::new(bounds.lower_bound, bounds.upper_bound),
129                    )
130                }
131                Type::Bool | Type::Facet | Type::Bytes | Type::Json | Type::IpAddr => {
132                    Err(crate::TantivyError::InvalidArgument(format!(
133                        "unsupported value bytes type in json term value_bytes {:?}",
134                        term_value.typ()
135                    )))
136                }
137            }
138        } else if field_type.is_ip_addr() {
139            let parse_ip_from_bytes = |term: &Term| {
140                term.value().as_ip_addr().ok_or_else(|| {
141                    crate::TantivyError::InvalidArgument("Expected ip address".to_string())
142                })
143            };
144            let bounds: BoundsRange<Ipv6Addr> = self.bounds.map_bound_res(parse_ip_from_bytes)?;
145
146            let Some(ip_addr_column): Option<Column<Ipv6Addr>> =
147                reader.fast_fields().column_opt(&field_name)?
148            else {
149                return Ok(Box::new(EmptyScorer));
150            };
151            let value_range = bound_range_inclusive_ip(
152                &bounds.lower_bound,
153                &bounds.upper_bound,
154                ip_addr_column.min_value(),
155                ip_addr_column.max_value(),
156            );
157            let docset = RangeDocSet::new(value_range, ip_addr_column);
158            Ok(Box::new(ConstScorer::new(docset, boost)))
159        } else if field_type.is_str() {
160            let Some(str_dict_column): Option<StrColumn> = reader.fast_fields().str(&field_name)?
161            else {
162                return Ok(Box::new(EmptyScorer));
163            };
164            let dict = str_dict_column.dictionary();
165
166            let bounds = self.bounds.map_bound(get_value_bytes);
167            // Get term ids for terms
168            let (lower_bound, upper_bound) =
169                dict.term_bounds_to_ord(bounds.lower_bound, bounds.upper_bound)?;
170            let fast_field_reader = reader.fast_fields();
171            let Some((column, _col_type)) =
172                fast_field_reader.u64_lenient_for_type(None, &field_name)?
173            else {
174                return Ok(Box::new(EmptyScorer));
175            };
176            search_on_u64_ff(column, boost, BoundsRange::new(lower_bound, upper_bound))
177        } else {
178            assert!(
179                maps_to_u64_fastfield(field_type.value_type()),
180                "{field_type:?}"
181            );
182
183            let bounds = self.bounds.map_bound_res(|term| {
184                let value = term.value();
185                let val = if let Some(val) = value.as_u64() {
186                    val
187                } else if let Some(val) = value.as_i64() {
188                    val.to_u64()
189                } else if let Some(val) = value.as_f64() {
190                    val.to_u64()
191                } else if let Some(val) = value.as_date() {
192                    val.to_u64()
193                } else {
194                    return Err(TantivyError::InvalidArgument(format!(
195                        "Expected term with u64, i64, f64 or date, but got {term:?}"
196                    )));
197                };
198                Ok(val)
199            })?;
200
201            let fast_field_reader = reader.fast_fields();
202            let Some((column, _col_type)) = fast_field_reader.u64_lenient_for_type(
203                Some(&[
204                    ColumnType::U64,
205                    ColumnType::I64,
206                    ColumnType::F64,
207                    ColumnType::DateTime,
208                ]),
209                &field_name,
210            )?
211            else {
212                return Ok(Box::new(EmptyScorer));
213            };
214            search_on_u64_ff(
215                column,
216                boost,
217                BoundsRange::new(bounds.lower_bound, bounds.upper_bound),
218            )
219        }
220    }
221
222    fn explain(&self, reader: &SegmentReader, doc: DocId) -> crate::Result<Explanation> {
223        let mut scorer = self.scorer(reader, 1.0)?;
224        if scorer.seek(doc) != doc {
225            return Err(TantivyError::InvalidArgument(format!(
226                "Document #({doc}) does not match"
227            )));
228        }
229        let explanation = Explanation::new("Const", scorer.score());
230
231        Ok(explanation)
232    }
233}
234
235/// On numerical fields the column type may not match the user provided one.
236///
237/// Convert into fast field value space and search.
238fn search_on_json_numerical_field(
239    reader: &SegmentReader,
240    field_name: &str,
241    typ: Type,
242    bounds: BoundsRange<ValueBytes<Vec<u8>>>,
243    boost: Score,
244) -> crate::Result<Box<dyn Scorer>> {
245    // Since we don't know which type was interpolated for the internal column we
246    // have to check for all numeric types (only one exists)
247    let allowed_column_types: Option<&[ColumnType]> =
248        Some(&[ColumnType::F64, ColumnType::I64, ColumnType::U64]);
249    let fast_field_reader = reader.fast_fields();
250    let Some((column, col_type)) =
251        fast_field_reader.u64_lenient_for_type(allowed_column_types, field_name)?
252    else {
253        return Ok(Box::new(EmptyScorer));
254    };
255    let actual_column_type: NumericalType = col_type
256        .numerical_type()
257        .unwrap_or_else(|| panic!("internal error: couldn't cast to numerical_type: {col_type:?}"));
258
259    let bounds = match typ.numerical_type().unwrap() {
260        NumericalType::I64 => {
261            let bounds = bounds.map_bound(|term| (term.as_i64().unwrap()));
262            match actual_column_type {
263                NumericalType::I64 => bounds.map_bound(|&term| term.to_u64()),
264                NumericalType::U64 => {
265                    bounds.transform_inner(
266                        |&val| {
267                            if val < 0 {
268                                return TransformBound::NewBound(Bound::Unbounded);
269                            }
270                            TransformBound::Existing(val as u64)
271                        },
272                        |&val| {
273                            if val < 0 {
274                                // no hits case
275                                return TransformBound::NewBound(Bound::Excluded(0));
276                            }
277                            TransformBound::Existing(val as u64)
278                        },
279                    )
280                }
281                NumericalType::F64 => bounds.map_bound(|&term| (term as f64).to_u64()),
282            }
283        }
284        NumericalType::U64 => {
285            let bounds = bounds.map_bound(|term| (term.as_u64().unwrap()));
286            match actual_column_type {
287                NumericalType::U64 => bounds.map_bound(|&term| term.to_u64()),
288                NumericalType::I64 => {
289                    bounds.transform_inner(
290                        |&val| {
291                            if val > i64::MAX as u64 {
292                                // Actual no hits case
293                                return TransformBound::NewBound(Bound::Excluded(i64::MAX as u64));
294                            }
295                            TransformBound::Existing((val as i64).to_u64())
296                        },
297                        |&val| {
298                            if val > i64::MAX as u64 {
299                                return TransformBound::NewBound(Bound::Unbounded);
300                            }
301                            TransformBound::Existing((val as i64).to_u64())
302                        },
303                    )
304                }
305                NumericalType::F64 => bounds.map_bound(|&term| (term as f64).to_u64()),
306            }
307        }
308        NumericalType::F64 => {
309            let bounds = bounds.map_bound(|term| (term.as_f64().unwrap()));
310            match actual_column_type {
311                NumericalType::U64 => transform_from_f64_bounds::<u64>(&bounds),
312                NumericalType::I64 => transform_from_f64_bounds::<i64>(&bounds),
313                NumericalType::F64 => bounds.map_bound(|&term| term.to_u64()),
314            }
315        }
316    };
317    search_on_u64_ff(
318        column,
319        boost,
320        BoundsRange::new(bounds.lower_bound, bounds.upper_bound),
321    )
322}
323
324trait IntType {
325    fn min() -> Self;
326    fn max() -> Self;
327    fn to_f64(self) -> f64;
328    fn from_f64(val: f64) -> Self;
329}
330impl IntType for i64 {
331    fn min() -> Self {
332        Self::MIN
333    }
334    fn max() -> Self {
335        Self::MAX
336    }
337    fn to_f64(self) -> f64 {
338        self as f64
339    }
340    fn from_f64(val: f64) -> Self {
341        val as Self
342    }
343}
344impl IntType for u64 {
345    fn min() -> Self {
346        Self::MIN
347    }
348    fn max() -> Self {
349        Self::MAX
350    }
351    fn to_f64(self) -> f64 {
352        self as f64
353    }
354    fn from_f64(val: f64) -> Self {
355        val as Self
356    }
357}
358
359fn transform_from_f64_bounds<T: IntType + MonotonicallyMappableToU64>(
360    bounds: &BoundsRange<f64>,
361) -> BoundsRange<u64> {
362    bounds.transform_inner(
363        |&lower_bound| {
364            if lower_bound < T::min().to_f64() {
365                return TransformBound::NewBound(Bound::Unbounded);
366            }
367            if lower_bound > T::max().to_f64() {
368                // no hits case
369                return TransformBound::NewBound(Bound::Excluded(u64::MAX));
370            }
371
372            if lower_bound.fract() == 0.0 {
373                TransformBound::Existing(T::from_f64(lower_bound).to_u64())
374            } else {
375                TransformBound::NewBound(Bound::Included(T::from_f64(lower_bound.trunc()).to_u64()))
376            }
377        },
378        |&upper_bound| {
379            if upper_bound < T::min().to_f64() {
380                return TransformBound::NewBound(Bound::Unbounded);
381            }
382            if upper_bound > T::max().to_f64() {
383                // no hits case
384                return TransformBound::NewBound(Bound::Included(u64::MAX));
385            }
386            if upper_bound.fract() == 0.0 {
387                TransformBound::Existing(T::from_f64(upper_bound).to_u64())
388            } else {
389                TransformBound::NewBound(Bound::Included(T::from_f64(upper_bound.trunc()).to_u64()))
390            }
391        },
392    )
393}
394
395fn search_on_u64_ff(
396    column: Column<u64>,
397    boost: Score,
398    bounds: BoundsRange<u64>,
399) -> crate::Result<Box<dyn Scorer>> {
400    #[expect(clippy::reversed_empty_ranges)]
401    let value_range = bound_to_value_range(
402        &bounds.lower_bound,
403        &bounds.upper_bound,
404        column.min_value(),
405        column.max_value(),
406    )
407    .unwrap_or(1..=0); // empty range
408    if value_range.is_empty() {
409        return Ok(Box::new(EmptyScorer));
410    }
411    let docset = RangeDocSet::new(value_range, column);
412    Ok(Box::new(ConstScorer::new(docset, boost)))
413}
414
415/// Returns true if the type maps to a u64 fast field
416pub(crate) fn maps_to_u64_fastfield(typ: Type) -> bool {
417    match typ {
418        Type::U64 | Type::I64 | Type::F64 | Type::Bool | Type::Date => true,
419        Type::IpAddr => false,
420        Type::Str | Type::Facet | Type::Bytes | Type::Json => false,
421    }
422}
423
424fn bound_range_inclusive_ip(
425    lower_bound: &Bound<Ipv6Addr>,
426    upper_bound: &Bound<Ipv6Addr>,
427    min_value: Ipv6Addr,
428    max_value: Ipv6Addr,
429) -> RangeInclusive<Ipv6Addr> {
430    let start_value = match lower_bound {
431        Bound::Included(ip_addr) => *ip_addr,
432        Bound::Excluded(ip_addr) => Ipv6Addr::from(ip_addr.to_u128() + 1),
433        Bound::Unbounded => min_value,
434    };
435
436    let end_value = match upper_bound {
437        Bound::Included(ip_addr) => *ip_addr,
438        Bound::Excluded(ip_addr) => Ipv6Addr::from(ip_addr.to_u128() - 1),
439        Bound::Unbounded => max_value,
440    };
441    start_value..=end_value
442}
443
444// Returns None, if the range cannot be converted to a inclusive range (which equals to a empty
445// range).
446fn bound_to_value_range<T: MonotonicallyMappableToU64>(
447    lower_bound: &Bound<T>,
448    upper_bound: &Bound<T>,
449    min_value: T,
450    max_value: T,
451) -> Option<RangeInclusive<T>> {
452    let mut start_value = match lower_bound {
453        Bound::Included(val) => *val,
454        Bound::Excluded(val) => T::from_u64(val.to_u64().checked_add(1)?),
455        Bound::Unbounded => min_value,
456    };
457    if start_value.partial_cmp(&min_value) == Some(std::cmp::Ordering::Less) {
458        start_value = min_value;
459    }
460    let end_value = match upper_bound {
461        Bound::Included(val) => *val,
462        Bound::Excluded(val) => T::from_u64(val.to_u64().checked_sub(1)?),
463        Bound::Unbounded => max_value,
464    };
465    Some(start_value..=end_value)
466}
467
468#[cfg(test)]
469mod tests {
470    use std::ops::{Bound, RangeInclusive};
471
472    use common::bounds::BoundsRange;
473    use common::DateTime;
474    use proptest::prelude::*;
475    use rand::rngs::StdRng;
476    use rand::seq::SliceRandom;
477    use rand::SeedableRng;
478    use time::format_description::well_known::Rfc3339;
479    use time::OffsetDateTime;
480
481    use crate::collector::{Count, TopDocs};
482    use crate::fastfield::FastValue;
483    use crate::query::range_query::range_query_fastfield::FastFieldRangeWeight;
484    use crate::query::{QueryParser, RangeQuery, Weight};
485    use crate::schema::{
486        DateOptions, Field, NumericOptions, Schema, SchemaBuilder, FAST, INDEXED, STORED, STRING,
487        TEXT,
488    };
489    use crate::{Index, IndexWriter, TantivyDocument, Term, TERMINATED};
490
491    #[test]
492    fn test_text_field_ff_range_query() -> crate::Result<()> {
493        let mut schema_builder = Schema::builder();
494        schema_builder.add_text_field("title", TEXT | FAST);
495        let schema = schema_builder.build();
496        let index = Index::create_in_ram(schema.clone());
497        let mut index_writer = index.writer_for_tests()?;
498        let title = schema.get_field("title").unwrap();
499        index_writer.add_document(doc!(
500          title => "bbb"
501        ))?;
502        index_writer.add_document(doc!(
503          title => "ddd"
504        ))?;
505        index_writer.commit()?;
506        let reader = index.reader()?;
507        let searcher = reader.searcher();
508        let query_parser = QueryParser::for_index(&index, vec![title]);
509
510        let test_query = |query, num_hits| {
511            let query = query_parser.parse_query(query).unwrap();
512            let top_docs = searcher.search(&query, &TopDocs::with_limit(10)).unwrap();
513            assert_eq!(top_docs.len(), num_hits);
514        };
515
516        test_query("title:[aaa TO ccc]", 1);
517        test_query("title:[aaa TO bbb]", 1);
518        test_query("title:[bbb TO bbb]", 1);
519        test_query("title:[bbb TO ddd]", 2);
520        test_query("title:[bbb TO eee]", 2);
521        test_query("title:[bb TO eee]", 2);
522        test_query("title:[ccc TO ccc]", 0);
523        test_query("title:[ccc TO ddd]", 1);
524        test_query("title:[ccc TO eee]", 1);
525
526        test_query("title:[aaa TO *}", 2);
527        test_query("title:[bbb TO *]", 2);
528        test_query("title:[bb TO *]", 2);
529        test_query("title:[ccc TO *]", 1);
530        test_query("title:[ddd TO *]", 1);
531        test_query("title:[dddd TO *]", 0);
532
533        test_query("title:{aaa TO *}", 2);
534        test_query("title:{bbb TO *]", 1);
535        test_query("title:{bb TO *]", 2);
536        test_query("title:{ccc TO *]", 1);
537        test_query("title:{ddd TO *]", 0);
538        test_query("title:{dddd TO *]", 0);
539
540        test_query("title:[* TO bb]", 0);
541        test_query("title:[* TO bbb]", 1);
542        test_query("title:[* TO ccc]", 1);
543        test_query("title:[* TO ddd]", 2);
544        test_query("title:[* TO ddd}", 1);
545        test_query("title:[* TO eee]", 2);
546
547        Ok(())
548    }
549
550    #[test]
551    fn test_date_range_query() {
552        let mut schema_builder = Schema::builder();
553        let options = DateOptions::default()
554            .set_precision(common::DateTimePrecision::Microseconds)
555            .set_fast();
556        let date_field = schema_builder.add_date_field("date", options);
557        let schema = schema_builder.build();
558
559        let index = Index::create_in_ram(schema.clone());
560        {
561            let mut index_writer = index.writer_with_num_threads(1, 50_000_000).unwrap();
562            // This is added a string and creates a string column!
563            index_writer
564                .add_document(doc!(date_field => DateTime::from_utc(
565                    OffsetDateTime::parse("2022-12-01T00:00:01Z", &Rfc3339).unwrap(),
566                )))
567                .unwrap();
568            index_writer
569                .add_document(doc!(date_field => DateTime::from_utc(
570                    OffsetDateTime::parse("2023-12-01T00:00:01Z", &Rfc3339).unwrap(),
571                )))
572                .unwrap();
573            index_writer
574                .add_document(doc!(date_field => DateTime::from_utc(
575                    OffsetDateTime::parse("2015-02-01T00:00:00.001Z", &Rfc3339).unwrap(),
576                )))
577                .unwrap();
578            index_writer.commit().unwrap();
579        }
580
581        // Date field
582        let dt1 =
583            DateTime::from_utc(OffsetDateTime::parse("2022-12-01T00:00:01Z", &Rfc3339).unwrap());
584        let dt2 =
585            DateTime::from_utc(OffsetDateTime::parse("2023-12-01T00:00:01Z", &Rfc3339).unwrap());
586        let dt3 = DateTime::from_utc(
587            OffsetDateTime::parse("2015-02-01T00:00:00.001Z", &Rfc3339).unwrap(),
588        );
589        let dt4 = DateTime::from_utc(
590            OffsetDateTime::parse("2015-02-01T00:00:00.002Z", &Rfc3339).unwrap(),
591        );
592
593        let reader = index.reader().unwrap();
594        let searcher = reader.searcher();
595        let query_parser = QueryParser::for_index(&index, vec![date_field]);
596        let test_query = |query, num_hits| {
597            let query = query_parser.parse_query(query).unwrap();
598            let top_docs = searcher.search(&query, &TopDocs::with_limit(10)).unwrap();
599            assert_eq!(top_docs.len(), num_hits);
600        };
601
602        test_query(
603            "date:[2015-02-01T00:00:00.001Z TO 2015-02-01T00:00:00.001Z]",
604            1,
605        );
606        test_query(
607            "date:[2015-02-01T00:00:00.001Z TO 2015-02-01T00:00:00.002Z}",
608            1,
609        );
610        test_query(
611            "date:[2015-02-01T00:00:00.001Z TO 2015-02-01T00:00:00.002Z]",
612            1,
613        );
614        test_query(
615            "date:{2015-02-01T00:00:00.001Z TO 2015-02-01T00:00:00.002Z]",
616            0,
617        );
618
619        let count = |range_query: RangeQuery| searcher.search(&range_query, &Count).unwrap();
620        assert_eq!(
621            count(RangeQuery::new(
622                Bound::Included(Term::from_field_date(date_field, dt3)),
623                Bound::Excluded(Term::from_field_date(date_field, dt4)),
624            )),
625            1
626        );
627        assert_eq!(
628            count(RangeQuery::new(
629                Bound::Included(Term::from_field_date(date_field, dt3)),
630                Bound::Included(Term::from_field_date(date_field, dt4)),
631            )),
632            1
633        );
634        assert_eq!(
635            count(RangeQuery::new(
636                Bound::Included(Term::from_field_date(date_field, dt1)),
637                Bound::Included(Term::from_field_date(date_field, dt2)),
638            )),
639            2
640        );
641        assert_eq!(
642            count(RangeQuery::new(
643                Bound::Included(Term::from_field_date(date_field, dt1)),
644                Bound::Excluded(Term::from_field_date(date_field, dt2)),
645            )),
646            1
647        );
648        assert_eq!(
649            count(RangeQuery::new(
650                Bound::Excluded(Term::from_field_date(date_field, dt1)),
651                Bound::Excluded(Term::from_field_date(date_field, dt2)),
652            )),
653            0
654        );
655    }
656
657    fn get_json_term<T: FastValue>(field: Field, path: &str, value: T) -> Term {
658        let mut term = Term::from_field_json_path(field, path, true);
659        term.append_type_and_fast_value(value);
660        term
661    }
662
663    #[test]
664    fn mixed_numerical_test() {
665        let mut schema_builder = Schema::builder();
666        schema_builder.add_i64_field("id_i64", STORED | FAST);
667        schema_builder.add_u64_field("id_u64", STORED | FAST);
668        schema_builder.add_f64_field("id_f64", STORED | FAST);
669        let schema = schema_builder.build();
670
671        fn get_json_term<T: FastValue>(schema: &Schema, path: &str, value: T) -> Term {
672            let field = schema.get_field(path).unwrap();
673            Term::from_fast_value(field, &value)
674            // term.append_type_and_fast_value(value);
675            // term
676        }
677        let index = Index::create_in_ram(schema.clone());
678        {
679            let mut index_writer = index.writer_with_num_threads(1, 50_000_000).unwrap();
680
681            let doc = json!({
682                "id_u64": 0,
683                "id_i64": 50,
684            });
685            let doc = TantivyDocument::parse_json(&schema, &serde_json::to_string(&doc).unwrap())
686                .unwrap();
687            index_writer.add_document(doc).unwrap();
688            let doc = json!({
689                "id_u64": 10,
690                "id_i64": 1000,
691            });
692            let doc = TantivyDocument::parse_json(&schema, &serde_json::to_string(&doc).unwrap())
693                .unwrap();
694            index_writer.add_document(doc).unwrap();
695
696            index_writer.commit().unwrap();
697        }
698
699        let reader = index.reader().unwrap();
700        let searcher = reader.searcher();
701        let count = |range_query: RangeQuery| searcher.search(&range_query, &Count).unwrap();
702
703        // u64 on u64
704        assert_eq!(
705            count(RangeQuery::new(
706                Bound::Included(get_json_term(&schema, "id_u64", 10u64)),
707                Bound::Included(get_json_term(&schema, "id_u64", 10u64)),
708            )),
709            1
710        );
711        assert_eq!(
712            count(RangeQuery::new(
713                Bound::Included(get_json_term(&schema, "id_u64", 9u64)),
714                Bound::Excluded(get_json_term(&schema, "id_u64", 10u64)),
715            )),
716            0
717        );
718
719        // i64 on i64
720        assert_eq!(
721            count(RangeQuery::new(
722                Bound::Included(get_json_term(&schema, "id_i64", 50i64)),
723                Bound::Included(get_json_term(&schema, "id_i64", 1000i64)),
724            )),
725            2
726        );
727        assert_eq!(
728            count(RangeQuery::new(
729                Bound::Included(get_json_term(&schema, "id_i64", 50i64)),
730                Bound::Excluded(get_json_term(&schema, "id_i64", 1000i64)),
731            )),
732            1
733        );
734    }
735
736    #[test]
737    fn json_range_mixed_val() {
738        let mut schema_builder = Schema::builder();
739        let json_field = schema_builder.add_json_field("json", TEXT | STORED | FAST);
740        let schema = schema_builder.build();
741
742        let index = Index::create_in_ram(schema);
743        {
744            let mut index_writer = index.writer_with_num_threads(1, 50_000_000).unwrap();
745            let doc = json!({
746                "mixed_val": 10000,
747            });
748            index_writer.add_document(doc!(json_field => doc)).unwrap();
749            let doc = json!({
750                "mixed_val": 20000,
751            });
752            index_writer.add_document(doc!(json_field => doc)).unwrap();
753            let doc = json!({
754                "mixed_val": "1000a",
755            });
756            index_writer.add_document(doc!(json_field => doc)).unwrap();
757            let doc = json!({
758                "mixed_val": "2000a",
759            });
760            index_writer.add_document(doc!(json_field => doc)).unwrap();
761            index_writer.commit().unwrap();
762        }
763        let reader = index.reader().unwrap();
764        let searcher = reader.searcher();
765        let count = |range_query: RangeQuery| searcher.search(&range_query, &Count).unwrap();
766
767        assert_eq!(
768            count(RangeQuery::new(
769                Bound::Included(get_json_term(json_field, "mixed_val", 10000u64)),
770                Bound::Included(get_json_term(json_field, "mixed_val", 20000u64)),
771            )),
772            2
773        );
774        fn get_json_term_str(field: Field, path: &str, value: &str) -> Term {
775            let mut term = Term::from_field_json_path(field, path, true);
776            term.append_type_and_str(value);
777            term
778        }
779        assert_eq!(
780            count(RangeQuery::new(
781                Bound::Included(get_json_term_str(json_field, "mixed_val", "1000a")),
782                Bound::Included(get_json_term_str(json_field, "mixed_val", "2000b")),
783            )),
784            2
785        );
786        assert_eq!(
787            count(RangeQuery::new(
788                Bound::Included(get_json_term_str(json_field, "mixed_val", "1000")),
789                Bound::Included(get_json_term_str(json_field, "mixed_val", "2000a")),
790            )),
791            2
792        );
793    }
794
795    #[test]
796    fn json_range_test() {
797        let mut schema_builder = Schema::builder();
798        let json_field = schema_builder.add_json_field("json", TEXT | STORED | FAST);
799        let schema = schema_builder.build();
800
801        let index = Index::create_in_ram(schema);
802        let u64_val = u64::MAX - 1;
803        {
804            let mut index_writer = index.writer_with_num_threads(1, 50_000_000).unwrap();
805            let doc = json!({
806                "id_u64": 0,
807                "id_f64": 10.5,
808                "id_i64": -100,
809                "date": "2022-12-01T00:00:01Z"
810            });
811            index_writer.add_document(doc!(json_field => doc)).unwrap();
812            let doc = json!({
813                "id_u64": u64_val,
814                "id_f64": 1000.5,
815                "id_i64": 1000,
816                "date": "2023-12-01T00:00:01Z"
817            });
818            index_writer.add_document(doc!(json_field => doc)).unwrap();
819            let doc = json!({
820                "date": "2015-02-01T00:00:00.001Z"
821            });
822            index_writer.add_document(doc!(json_field => doc)).unwrap();
823
824            index_writer.commit().unwrap();
825        }
826
827        let reader = index.reader().unwrap();
828        let searcher = reader.searcher();
829        let count = |range_query: RangeQuery| searcher.search(&range_query, &Count).unwrap();
830
831        // u64 on u64
832        assert_eq!(
833            count(RangeQuery::new(
834                Bound::Included(get_json_term(json_field, "id_u64", u64_val)),
835                Bound::Included(get_json_term(json_field, "id_u64", u64_val)),
836            )),
837            1
838        );
839        assert_eq!(
840            count(RangeQuery::new(
841                Bound::Included(get_json_term(json_field, "id_u64", u64_val)),
842                Bound::Excluded(get_json_term(json_field, "id_u64", u64_val)),
843            )),
844            0
845        );
846        // f64 on u64 field
847        assert_eq!(
848            count(RangeQuery::new(
849                // We need to subtract since there is some inaccuracy
850                Bound::Included(get_json_term(
851                    json_field,
852                    "id_u64",
853                    (u64_val - 10000) as f64
854                )),
855                Bound::Included(get_json_term(json_field, "id_u64", (u64_val) as f64)),
856            )),
857            1
858        );
859        // i64 on u64
860        assert_eq!(
861            count(RangeQuery::new(
862                Bound::Included(get_json_term(json_field, "id_u64", 0_i64)),
863                Bound::Included(get_json_term(json_field, "id_u64", 0_i64)),
864            )),
865            1
866        );
867        assert_eq!(
868            count(RangeQuery::new(
869                Bound::Included(get_json_term(json_field, "id_u64", 1_i64)),
870                Bound::Included(get_json_term(json_field, "id_u64", 1_i64)),
871            )),
872            0
873        );
874        // u64 on f64
875        assert_eq!(
876            count(RangeQuery::new(
877                Bound::Included(get_json_term(json_field, "id_f64", 10_u64)),
878                Bound::Included(get_json_term(json_field, "id_f64", 11_u64)),
879            )),
880            1
881        );
882        assert_eq!(
883            count(RangeQuery::new(
884                Bound::Included(get_json_term(json_field, "id_f64", 10_u64)),
885                Bound::Included(get_json_term(json_field, "id_f64", 2000_u64)),
886            )),
887            2
888        );
889        // i64 on f64
890        assert_eq!(
891            count(RangeQuery::new(
892                Bound::Included(get_json_term(json_field, "id_f64", 10_i64)),
893                Bound::Included(get_json_term(json_field, "id_f64", 2000_i64)),
894            )),
895            2
896        );
897
898        // i64 on i64
899        assert_eq!(
900            count(RangeQuery::new(
901                Bound::Included(get_json_term(json_field, "id_i64", -1000i64)),
902                Bound::Included(get_json_term(json_field, "id_i64", 1000i64)),
903            )),
904            2
905        );
906        assert_eq!(
907            count(RangeQuery::new(
908                Bound::Included(get_json_term(json_field, "id_i64", 1000i64)),
909                Bound::Excluded(get_json_term(json_field, "id_i64", 1001i64)),
910            )),
911            1
912        );
913
914        // u64 on i64
915        assert_eq!(
916            count(RangeQuery::new(
917                Bound::Included(get_json_term(json_field, "id_i64", 0_u64)),
918                Bound::Included(get_json_term(json_field, "id_i64", 1000u64)),
919            )),
920            1
921        );
922        assert_eq!(
923            count(RangeQuery::new(
924                Bound::Included(get_json_term(json_field, "id_i64", 0_u64)),
925                Bound::Included(get_json_term(json_field, "id_i64", 999u64)),
926            )),
927            0
928        );
929        // f64 on i64 field
930        assert_eq!(
931            count(RangeQuery::new(
932                Bound::Included(get_json_term(json_field, "id_i64", -1000.0)),
933                Bound::Included(get_json_term(json_field, "id_i64", 1000.0)),
934            )),
935            2
936        );
937        assert_eq!(
938            count(RangeQuery::new(
939                Bound::Included(get_json_term(json_field, "id_i64", -1000.0f64)),
940                Bound::Excluded(get_json_term(json_field, "id_i64", 1000.0f64)),
941            )),
942            1
943        );
944        assert_eq!(
945            count(RangeQuery::new(
946                Bound::Included(get_json_term(json_field, "id_i64", -1000.0f64)),
947                Bound::Included(get_json_term(json_field, "id_i64", 1000.0f64)),
948            )),
949            2
950        );
951        assert_eq!(
952            count(RangeQuery::new(
953                Bound::Included(get_json_term(json_field, "id_i64", -1000.0f64)),
954                Bound::Excluded(get_json_term(json_field, "id_i64", 1000.01f64)),
955            )),
956            2
957        );
958        assert_eq!(
959            count(RangeQuery::new(
960                Bound::Included(get_json_term(json_field, "id_i64", -1000.0f64)),
961                Bound::Included(get_json_term(json_field, "id_i64", 999.99f64)),
962            )),
963            1
964        );
965        assert_eq!(
966            count(RangeQuery::new(
967                Bound::Excluded(get_json_term(json_field, "id_i64", 999.9)),
968                Bound::Excluded(get_json_term(json_field, "id_i64", 1000.1)),
969            )),
970            1
971        );
972
973        let reader = index.reader().unwrap();
974        let searcher = reader.searcher();
975        let query_parser = QueryParser::for_index(&index, vec![json_field]);
976        let test_query = |query, num_hits| {
977            let query = query_parser.parse_query(query).unwrap();
978            let top_docs = searcher.search(&query, &TopDocs::with_limit(10)).unwrap();
979            assert_eq!(top_docs.len(), num_hits);
980        };
981
982        test_query(
983            "json.date:[2015-02-01T00:00:00.001Z TO 2015-02-01T00:00:00.001Z]",
984            1,
985        );
986        test_query(
987            "json.date:[2015-02-01T00:00:00.001Z TO 2015-02-01T00:00:00.002Z}",
988            1,
989        );
990        test_query(
991            "json.date:[2015-02-01T00:00:00.001Z TO 2015-02-01T00:00:00.002Z]",
992            1,
993        );
994        test_query(
995            "json.date:{2015-02-01T00:00:00.001Z TO 2015-02-01T00:00:00.002Z]",
996            0,
997        );
998
999        // Date field
1000        let dt1 =
1001            DateTime::from_utc(OffsetDateTime::parse("2022-12-01T00:00:01Z", &Rfc3339).unwrap());
1002        let dt2 =
1003            DateTime::from_utc(OffsetDateTime::parse("2023-12-01T00:00:01Z", &Rfc3339).unwrap());
1004
1005        assert_eq!(
1006            count(RangeQuery::new(
1007                Bound::Included(get_json_term(json_field, "date", dt1)),
1008                Bound::Included(get_json_term(json_field, "date", dt2)),
1009            )),
1010            2
1011        );
1012        assert_eq!(
1013            count(RangeQuery::new(
1014                Bound::Included(get_json_term(json_field, "date", dt1)),
1015                Bound::Excluded(get_json_term(json_field, "date", dt2)),
1016            )),
1017            1
1018        );
1019        assert_eq!(
1020            count(RangeQuery::new(
1021                Bound::Excluded(get_json_term(json_field, "date", dt1)),
1022                Bound::Excluded(get_json_term(json_field, "date", dt2)),
1023            )),
1024            0
1025        );
1026        // Date precision test. We don't want to truncate the precision
1027        let dt3 = DateTime::from_utc(
1028            OffsetDateTime::parse("2015-02-01T00:00:00.001Z", &Rfc3339).unwrap(),
1029        );
1030        let dt4 = DateTime::from_utc(
1031            OffsetDateTime::parse("2015-02-01T00:00:00.002Z", &Rfc3339).unwrap(),
1032        );
1033        let query = RangeQuery::new(
1034            Bound::Included(get_json_term(json_field, "date", dt3)),
1035            Bound::Excluded(get_json_term(json_field, "date", dt4)),
1036        );
1037        assert_eq!(count(query), 1);
1038    }
1039
1040    #[derive(Clone, Debug)]
1041    pub struct Doc {
1042        pub id_name: String,
1043        pub id: u64,
1044    }
1045
1046    fn operation_strategy() -> impl Strategy<Value = Doc> {
1047        prop_oneof![
1048            (0u64..10_000u64).prop_map(doc_from_id_1),
1049            (1u64..10_000u64).prop_map(doc_from_id_2),
1050        ]
1051    }
1052
1053    fn doc_from_id_1(id: u64) -> Doc {
1054        let id = id * 1000;
1055        Doc {
1056            id_name: format!("id_name{id:010}"),
1057            id,
1058        }
1059    }
1060    fn doc_from_id_2(id: u64) -> Doc {
1061        let id = id * 1000;
1062        Doc {
1063            id_name: format!("id_name{:010}", id - 1),
1064            id,
1065        }
1066    }
1067
1068    proptest! {
1069        #![proptest_config(ProptestConfig::with_cases(10))]
1070        #[test]
1071        fn test_range_for_docs_prop(ops in proptest::collection::vec(operation_strategy(), 1..1000)) {
1072            assert!(test_id_range_for_docs(ops).is_ok());
1073        }
1074    }
1075
1076    #[test]
1077    fn range_regression1_test() {
1078        let ops = vec![doc_from_id_1(0)];
1079        assert!(test_id_range_for_docs(ops).is_ok());
1080    }
1081
1082    #[test]
1083    fn range_regression1_test_json() {
1084        let ops = vec![doc_from_id_1(0)];
1085        assert!(test_id_range_for_docs_json(ops).is_ok());
1086    }
1087
1088    #[test]
1089    fn test_range_regression2() {
1090        let ops = vec![
1091            doc_from_id_1(52),
1092            doc_from_id_1(63),
1093            doc_from_id_1(12),
1094            doc_from_id_2(91),
1095            doc_from_id_2(33),
1096        ];
1097        assert!(test_id_range_for_docs(ops).is_ok());
1098    }
1099
1100    #[test]
1101    fn test_range_regression3() {
1102        let ops = vec![doc_from_id_1(9), doc_from_id_1(0), doc_from_id_1(13)];
1103        assert!(test_id_range_for_docs(ops).is_ok());
1104    }
1105
1106    #[test]
1107    fn test_range_regression_simplified() {
1108        let mut schema_builder = SchemaBuilder::new();
1109        let field = schema_builder.add_u64_field("test_field", FAST);
1110        let schema = schema_builder.build();
1111        let index = Index::create_in_ram(schema);
1112        let mut writer: IndexWriter = index.writer_for_tests().unwrap();
1113        writer.add_document(doc!(field=>52_000u64)).unwrap();
1114        writer.commit().unwrap();
1115        let searcher = index.reader().unwrap().searcher();
1116        let range_query = FastFieldRangeWeight::new(BoundsRange::new(
1117            Bound::Included(Term::from_field_u64(field, 50_000)),
1118            Bound::Included(Term::from_field_u64(field, 50_002)),
1119        ));
1120        let scorer = range_query
1121            .scorer(searcher.segment_reader(0), 1.0f32)
1122            .unwrap();
1123        assert_eq!(scorer.doc(), TERMINATED);
1124    }
1125
1126    #[test]
1127    fn range_regression3_test() {
1128        let ops = vec![doc_from_id_1(1), doc_from_id_1(2), doc_from_id_1(3)];
1129        assert!(test_id_range_for_docs(ops).is_ok());
1130    }
1131
1132    #[test]
1133    fn range_regression4_test() {
1134        let ops = vec![doc_from_id_2(100)];
1135        assert!(test_id_range_for_docs(ops).is_ok());
1136    }
1137
1138    pub fn create_index_from_docs(docs: &[Doc], json_field: bool) -> Index {
1139        let mut schema_builder = Schema::builder();
1140        if json_field {
1141            let json_field = schema_builder.add_json_field("json", TEXT | STORED | FAST);
1142            let schema = schema_builder.build();
1143
1144            let index = Index::create_in_ram(schema);
1145
1146            {
1147                let mut index_writer = index.writer_with_num_threads(1, 50_000_000).unwrap();
1148                for doc in docs.iter() {
1149                    let doc = json!({
1150                        "ids_i64": doc.id as i64,
1151                        "ids_i64": doc.id as i64,
1152                        "ids_f64": doc.id as f64,
1153                        "ids_f64": doc.id as f64,
1154                        "ids": doc.id,
1155                        "ids": doc.id,
1156                        "id": doc.id,
1157                        "id_f64": doc.id as f64,
1158                        "id_i64": doc.id as i64,
1159                        "id_name": doc.id_name.to_string(),
1160                        "id_name_fast": doc.id_name.to_string(),
1161                    });
1162                    index_writer.add_document(doc!(json_field => doc)).unwrap();
1163                }
1164
1165                index_writer.commit().unwrap();
1166            }
1167            index
1168        } else {
1169            let id_u64_field = schema_builder.add_u64_field("id", INDEXED | STORED | FAST);
1170            let ids_u64_field = schema_builder
1171                .add_u64_field("ids", NumericOptions::default().set_fast().set_indexed());
1172
1173            let id_f64_field = schema_builder.add_f64_field("id_f64", INDEXED | STORED | FAST);
1174            let ids_f64_field = schema_builder.add_f64_field(
1175                "ids_f64",
1176                NumericOptions::default().set_fast().set_indexed(),
1177            );
1178
1179            let id_i64_field = schema_builder.add_i64_field("id_i64", INDEXED | STORED | FAST);
1180            let ids_i64_field = schema_builder.add_i64_field(
1181                "ids_i64",
1182                NumericOptions::default().set_fast().set_indexed(),
1183            );
1184
1185            let text_field = schema_builder.add_text_field("id_name", STRING | STORED);
1186            let text_field2 = schema_builder.add_text_field("id_name_fast", STRING | STORED | FAST);
1187            let schema = schema_builder.build();
1188
1189            let index = Index::create_in_ram(schema);
1190
1191            {
1192                let mut index_writer = index.writer_with_num_threads(1, 50_000_000).unwrap();
1193                for doc in docs.iter() {
1194                    index_writer
1195                        .add_document(doc!(
1196                            ids_i64_field => doc.id as i64,
1197                            ids_i64_field => doc.id as i64,
1198                            ids_f64_field => doc.id as f64,
1199                            ids_f64_field => doc.id as f64,
1200                            ids_u64_field => doc.id,
1201                            ids_u64_field => doc.id,
1202                            id_u64_field => doc.id,
1203                            id_f64_field => doc.id as f64,
1204                            id_i64_field => doc.id as i64,
1205                            text_field => doc.id_name.to_string(),
1206                            text_field2 => doc.id_name.to_string(),
1207                        ))
1208                        .unwrap();
1209                }
1210
1211                index_writer.commit().unwrap();
1212            }
1213            index
1214        }
1215    }
1216
1217    fn test_id_range_for_docs(docs: Vec<Doc>) -> crate::Result<()> {
1218        test_id_range_for_docs_with_opt(docs, false)
1219    }
1220    fn test_id_range_for_docs_json(docs: Vec<Doc>) -> crate::Result<()> {
1221        test_id_range_for_docs_with_opt(docs, true)
1222    }
1223
1224    fn test_id_range_for_docs_with_opt(docs: Vec<Doc>, json: bool) -> crate::Result<()> {
1225        let index = create_index_from_docs(&docs, json);
1226        let reader = index.reader().unwrap();
1227        let searcher = reader.searcher();
1228
1229        let mut rng: StdRng = StdRng::from_seed([1u8; 32]);
1230
1231        let get_num_hits = |query| searcher.search(&query, &Count).unwrap();
1232        let query_from_text = |text: &str| {
1233            QueryParser::for_index(&index, vec![])
1234                .parse_query(text)
1235                .unwrap()
1236        };
1237
1238        let field_path = |field: &str| {
1239            if json {
1240                format!("json.{field}")
1241            } else {
1242                field.to_string()
1243            }
1244        };
1245
1246        let gen_query_inclusive = |field: &str, range: RangeInclusive<u64>| {
1247            format!(
1248                "{}:[{} TO {}]",
1249                field_path(field),
1250                range.start(),
1251                range.end()
1252            )
1253        };
1254        let gen_query_exclusive = |field: &str, range: RangeInclusive<u64>| {
1255            format!(
1256                "{}:{{{} TO {}}}",
1257                field_path(field),
1258                range.start(),
1259                range.end()
1260            )
1261        };
1262
1263        let test_sample = |sample_docs: Vec<Doc>| {
1264            let mut ids: Vec<u64> = sample_docs.iter().map(|doc| doc.id).collect();
1265            ids.sort();
1266            let expected_num_hits = docs
1267                .iter()
1268                .filter(|doc| (ids[0]..=ids[1]).contains(&doc.id))
1269                .count();
1270
1271            let query = gen_query_inclusive("id", ids[0]..=ids[1]);
1272            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1273
1274            let query = gen_query_inclusive("ids", ids[0]..=ids[1]);
1275            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1276
1277            // Text query
1278            {
1279                let test_text_query = |field_name: &str| {
1280                    let mut id_names: Vec<&str> =
1281                        sample_docs.iter().map(|doc| doc.id_name.as_str()).collect();
1282                    id_names.sort();
1283                    let expected_num_hits = docs
1284                        .iter()
1285                        .filter(|doc| (id_names[0]..=id_names[1]).contains(&doc.id_name.as_str()))
1286                        .count();
1287                    let query = format!(
1288                        "{}:[{} TO {}]",
1289                        field_path(field_name),
1290                        id_names[0],
1291                        id_names[1]
1292                    );
1293                    assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1294                };
1295
1296                test_text_query("id_name");
1297                test_text_query("id_name_fast");
1298            }
1299
1300            // Exclusive range
1301            let expected_num_hits = docs
1302                .iter()
1303                .filter(|doc| {
1304                    (ids[0].saturating_add(1)..=ids[1].saturating_sub(1)).contains(&doc.id)
1305                })
1306                .count();
1307
1308            let query = gen_query_exclusive("id", ids[0]..=ids[1]);
1309            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1310
1311            let query = gen_query_exclusive("ids", ids[0]..=ids[1]);
1312            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1313
1314            // Intersection search
1315            let id_filter = sample_docs[0].id_name.to_string();
1316            let expected_num_hits = docs
1317                .iter()
1318                .filter(|doc| (ids[0]..=ids[1]).contains(&doc.id) && doc.id_name == id_filter)
1319                .count();
1320            let query = format!(
1321                "{} AND {}:{}",
1322                gen_query_inclusive("id", ids[0]..=ids[1]),
1323                field_path("id_name"),
1324                &id_filter
1325            );
1326            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1327            let query = format!(
1328                "{} AND {}:{}",
1329                gen_query_inclusive("id_f64", ids[0]..=ids[1]),
1330                field_path("id_name"),
1331                &id_filter
1332            );
1333            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1334            let query = format!(
1335                "{} AND {}:{}",
1336                gen_query_inclusive("id_i64", ids[0]..=ids[1]),
1337                field_path("id_name"),
1338                &id_filter
1339            );
1340            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1341
1342            // Intersection search on multivalue id field
1343            let id_filter = sample_docs[0].id_name.to_string();
1344            let query = format!(
1345                "{} AND {}:{}",
1346                gen_query_inclusive("ids", ids[0]..=ids[1]),
1347                field_path("id_name"),
1348                &id_filter
1349            );
1350            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1351            let query = format!(
1352                "{} AND {}:{}",
1353                gen_query_inclusive("ids_f64", ids[0]..=ids[1]),
1354                field_path("id_name"),
1355                &id_filter
1356            );
1357            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1358            let query = format!(
1359                "{} AND {}:{}",
1360                gen_query_inclusive("ids_i64", ids[0]..=ids[1]),
1361                field_path("id_name"),
1362                &id_filter
1363            );
1364            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1365        };
1366
1367        test_sample(vec![docs[0].clone(), docs[0].clone()]);
1368
1369        let samples: Vec<_> = docs.choose_multiple(&mut rng, 3).collect();
1370
1371        if samples.len() > 1 {
1372            test_sample(vec![samples[0].clone(), samples[1].clone()]);
1373            test_sample(vec![samples[1].clone(), samples[1].clone()]);
1374        }
1375        if samples.len() > 2 {
1376            test_sample(vec![samples[1].clone(), samples[2].clone()]);
1377        }
1378
1379        Ok(())
1380    }
1381}
1382
1383#[cfg(test)]
1384pub(crate) mod ip_range_tests {
1385    use proptest::prelude::ProptestConfig;
1386    use proptest::strategy::Strategy;
1387    use proptest::{prop_oneof, proptest};
1388
1389    use super::*;
1390    use crate::collector::Count;
1391    use crate::query::QueryParser;
1392    use crate::schema::{Schema, FAST, INDEXED, STORED, STRING};
1393    use crate::{Index, IndexWriter};
1394
1395    #[derive(Clone, Debug)]
1396    pub struct Doc {
1397        pub id: String,
1398        pub ip: Ipv6Addr,
1399    }
1400
1401    fn operation_strategy() -> impl Strategy<Value = Doc> {
1402        prop_oneof![
1403            (0u64..10_000u64).prop_map(doc_from_id_1),
1404            (1u64..10_000u64).prop_map(doc_from_id_2),
1405        ]
1406    }
1407
1408    pub fn doc_from_id_1(id: u64) -> Doc {
1409        let id = id * 1000;
1410        Doc {
1411            // ip != id
1412            id: id.to_string(),
1413            ip: Ipv6Addr::from_u128(id as u128),
1414        }
1415    }
1416    fn doc_from_id_2(id: u64) -> Doc {
1417        let id = id * 1000;
1418        Doc {
1419            // ip != id
1420            id: (id - 1).to_string(),
1421            ip: Ipv6Addr::from_u128(id as u128),
1422        }
1423    }
1424
1425    proptest! {
1426        #![proptest_config(ProptestConfig::with_cases(10))]
1427        #[test]
1428        fn test_ip_range_for_docs_prop(ops in proptest::collection::vec(operation_strategy(), 1..1000)) {
1429            assert!(test_ip_range_for_docs(&ops).is_ok());
1430        }
1431    }
1432
1433    #[test]
1434    fn test_ip_range_regression1() {
1435        let ops = &[doc_from_id_1(0)];
1436        assert!(test_ip_range_for_docs(ops).is_ok());
1437    }
1438
1439    #[test]
1440    fn test_ip_range_regression2() {
1441        let ops = &[
1442            doc_from_id_1(52),
1443            doc_from_id_1(63),
1444            doc_from_id_1(12),
1445            doc_from_id_2(91),
1446            doc_from_id_2(33),
1447        ];
1448        assert!(test_ip_range_for_docs(ops).is_ok());
1449    }
1450
1451    #[test]
1452    fn test_ip_range_regression3() {
1453        let ops = &[doc_from_id_1(1), doc_from_id_1(2), doc_from_id_1(3)];
1454        assert!(test_ip_range_for_docs(ops).is_ok());
1455    }
1456
1457    #[test]
1458    fn test_ip_range_regression3_simple() {
1459        let mut schema_builder = Schema::builder();
1460        let ips_field = schema_builder.add_ip_addr_field("ips", FAST | INDEXED);
1461        let schema = schema_builder.build();
1462        let index = Index::create_in_ram(schema);
1463        let mut writer: IndexWriter = index.writer_for_tests().unwrap();
1464        let ip_addrs: Vec<Ipv6Addr> = [1000, 2000, 3000]
1465            .into_iter()
1466            .map(Ipv6Addr::from_u128)
1467            .collect();
1468        for &ip_addr in &ip_addrs {
1469            writer
1470                .add_document(doc!(ips_field=>ip_addr, ips_field=>ip_addr))
1471                .unwrap();
1472        }
1473        writer.commit().unwrap();
1474        let searcher = index.reader().unwrap().searcher();
1475        let range_weight = FastFieldRangeWeight::new(BoundsRange::new(
1476            Bound::Included(Term::from_field_ip_addr(ips_field, ip_addrs[1])),
1477            Bound::Included(Term::from_field_ip_addr(ips_field, ip_addrs[2])),
1478        ));
1479
1480        let count =
1481            crate::query::weight::Weight::count(&range_weight, searcher.segment_reader(0)).unwrap();
1482        assert_eq!(count, 2);
1483    }
1484
1485    pub fn create_index_from_ip_docs(docs: &[Doc]) -> Index {
1486        let mut schema_builder = Schema::builder();
1487        let ip_field = schema_builder.add_ip_addr_field("ip", STORED | FAST);
1488        let ips_field = schema_builder.add_ip_addr_field("ips", FAST | INDEXED);
1489        let text_field = schema_builder.add_text_field("id", STRING | STORED);
1490        let schema = schema_builder.build();
1491        let index = Index::create_in_ram(schema);
1492
1493        {
1494            let mut index_writer = index.writer_with_num_threads(2, 60_000_000).unwrap();
1495            for doc in docs.iter() {
1496                index_writer
1497                    .add_document(doc!(
1498                        ips_field => doc.ip,
1499                        ips_field => doc.ip,
1500                        ip_field => doc.ip,
1501                        text_field => doc.id.to_string(),
1502                    ))
1503                    .unwrap();
1504            }
1505
1506            index_writer.commit().unwrap();
1507        }
1508        index
1509    }
1510
1511    fn test_ip_range_for_docs(docs: &[Doc]) -> crate::Result<()> {
1512        let index = create_index_from_ip_docs(docs);
1513        let reader = index.reader().unwrap();
1514        let searcher = reader.searcher();
1515
1516        let get_num_hits = |query| searcher.search(&query, &Count).unwrap();
1517        let query_from_text = |text: &str| {
1518            QueryParser::for_index(&index, vec![])
1519                .parse_query(text)
1520                .unwrap()
1521        };
1522
1523        let gen_query_inclusive = |field: &str, ip_range: &RangeInclusive<Ipv6Addr>| {
1524            format!("{field}:[{} TO {}]", ip_range.start(), ip_range.end())
1525        };
1526
1527        let test_sample = |sample_docs: &[Doc]| {
1528            let mut ips: Vec<Ipv6Addr> = sample_docs.iter().map(|doc| doc.ip).collect();
1529            ips.sort();
1530            let ip_range = ips[0]..=ips[1];
1531            let expected_num_hits = docs
1532                .iter()
1533                .filter(|doc| (ips[0]..=ips[1]).contains(&doc.ip))
1534                .count();
1535
1536            let query = gen_query_inclusive("ip", &ip_range);
1537            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1538
1539            let query = gen_query_inclusive("ips", &ip_range);
1540            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1541
1542            // Intersection search
1543            let id_filter = sample_docs[0].id.to_string();
1544            let expected_num_hits = docs
1545                .iter()
1546                .filter(|doc| ip_range.contains(&doc.ip) && doc.id == id_filter)
1547                .count();
1548            let query = format!(
1549                "{} AND id:{}",
1550                gen_query_inclusive("ip", &ip_range),
1551                &id_filter
1552            );
1553            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1554
1555            // Intersection search on multivalue ip field
1556            let id_filter = sample_docs[0].id.to_string();
1557            let query = format!(
1558                "{} AND id:{}",
1559                gen_query_inclusive("ips", &ip_range),
1560                &id_filter
1561            );
1562            assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
1563        };
1564
1565        test_sample(&[docs[0].clone(), docs[0].clone()]);
1566        if docs.len() > 1 {
1567            test_sample(&[docs[0].clone(), docs[1].clone()]);
1568            test_sample(&[docs[1].clone(), docs[1].clone()]);
1569        }
1570        if docs.len() > 2 {
1571            test_sample(&[docs[1].clone(), docs[2].clone()]);
1572        }
1573
1574        Ok(())
1575    }
1576}
1577
1578#[cfg(all(test, feature = "unstable"))]
1579mod bench {
1580
1581    use rand::rngs::StdRng;
1582    use rand::{Rng, SeedableRng};
1583    use test::Bencher;
1584
1585    use super::tests::*;
1586    use super::*;
1587    use crate::collector::Count;
1588    use crate::query::QueryParser;
1589    use crate::Index;
1590
1591    fn get_index_0_to_100() -> Index {
1592        let mut rng = StdRng::from_seed([1u8; 32]);
1593        let num_vals = 100_000;
1594        let docs: Vec<_> = (0..num_vals)
1595            .map(|_i| {
1596                let id_name = if rng.gen_bool(0.01) {
1597                    "veryfew".to_string() // 1%
1598                } else if rng.gen_bool(0.1) {
1599                    "few".to_string() // 9%
1600                } else {
1601                    "many".to_string() // 90%
1602                };
1603                Doc {
1604                    id_name,
1605                    id: rng.gen_range(0..100),
1606                }
1607            })
1608            .collect();
1609
1610        create_index_from_docs(&docs, false)
1611    }
1612
1613    fn get_90_percent() -> RangeInclusive<u64> {
1614        0..=90
1615    }
1616
1617    fn get_10_percent() -> RangeInclusive<u64> {
1618        0..=10
1619    }
1620
1621    fn get_1_percent() -> RangeInclusive<u64> {
1622        10..=10
1623    }
1624
1625    fn execute_query(
1626        field: &str,
1627        id_range: RangeInclusive<u64>,
1628        suffix: &str,
1629        index: &Index,
1630    ) -> usize {
1631        let gen_query_inclusive = |from: &u64, to: &u64| {
1632            format!(
1633                "{}:[{} TO {}] {}",
1634                field,
1635                &from.to_string(),
1636                &to.to_string(),
1637                suffix
1638            )
1639        };
1640
1641        let query = gen_query_inclusive(id_range.start(), id_range.end());
1642        let query_from_text = |text: &str| {
1643            QueryParser::for_index(index, vec![])
1644                .parse_query(text)
1645                .unwrap()
1646        };
1647        let query = query_from_text(&query);
1648        let reader = index.reader().unwrap();
1649        let searcher = reader.searcher();
1650        searcher.search(&query, &(Count)).unwrap()
1651    }
1652
1653    #[bench]
1654    fn bench_id_range_hit_90_percent(bench: &mut Bencher) {
1655        let index = get_index_0_to_100();
1656        bench.iter(|| execute_query("id", get_90_percent(), "", &index));
1657    }
1658
1659    #[bench]
1660    fn bench_id_range_hit_10_percent(bench: &mut Bencher) {
1661        let index = get_index_0_to_100();
1662        bench.iter(|| execute_query("id", get_10_percent(), "", &index));
1663    }
1664
1665    #[bench]
1666    fn bench_id_range_hit_1_percent(bench: &mut Bencher) {
1667        let index = get_index_0_to_100();
1668        bench.iter(|| execute_query("id", get_1_percent(), "", &index));
1669    }
1670
1671    #[bench]
1672    fn bench_id_range_hit_10_percent_intersect_with_10_percent(bench: &mut Bencher) {
1673        let index = get_index_0_to_100();
1674        bench.iter(|| execute_query("id", get_10_percent(), "AND id_name:few", &index));
1675    }
1676
1677    #[bench]
1678    fn bench_id_range_hit_1_percent_intersect_with_10_percent(bench: &mut Bencher) {
1679        let index = get_index_0_to_100();
1680        bench.iter(|| execute_query("id", get_1_percent(), "AND id_name:few", &index));
1681    }
1682
1683    #[bench]
1684    fn bench_id_range_hit_1_percent_intersect_with_90_percent(bench: &mut Bencher) {
1685        let index = get_index_0_to_100();
1686        bench.iter(|| execute_query("id", get_1_percent(), "AND id_name:many", &index));
1687    }
1688
1689    #[bench]
1690    fn bench_id_range_hit_1_percent_intersect_with_1_percent(bench: &mut Bencher) {
1691        let index = get_index_0_to_100();
1692        bench.iter(|| execute_query("id", get_1_percent(), "AND id_name:veryfew", &index));
1693    }
1694
1695    #[bench]
1696    fn bench_id_range_hit_10_percent_intersect_with_90_percent(bench: &mut Bencher) {
1697        let index = get_index_0_to_100();
1698        bench.iter(|| execute_query("id", get_10_percent(), "AND id_name:many", &index));
1699    }
1700
1701    #[bench]
1702    fn bench_id_range_hit_90_percent_intersect_with_90_percent(bench: &mut Bencher) {
1703        let index = get_index_0_to_100();
1704        bench.iter(|| execute_query("id", get_90_percent(), "AND id_name:many", &index));
1705    }
1706
1707    #[bench]
1708    fn bench_id_range_hit_90_percent_intersect_with_10_percent(bench: &mut Bencher) {
1709        let index = get_index_0_to_100();
1710        bench.iter(|| execute_query("id", get_90_percent(), "AND id_name:few", &index));
1711    }
1712
1713    #[bench]
1714    fn bench_id_range_hit_90_percent_intersect_with_1_percent(bench: &mut Bencher) {
1715        let index = get_index_0_to_100();
1716        bench.iter(|| execute_query("id", get_90_percent(), "AND id_name:veryfew", &index));
1717    }
1718
1719    #[bench]
1720    fn bench_id_range_hit_90_percent_multi(bench: &mut Bencher) {
1721        let index = get_index_0_to_100();
1722        bench.iter(|| execute_query("ids", get_90_percent(), "", &index));
1723    }
1724
1725    #[bench]
1726    fn bench_id_range_hit_10_percent_multi(bench: &mut Bencher) {
1727        let index = get_index_0_to_100();
1728        bench.iter(|| execute_query("ids", get_10_percent(), "", &index));
1729    }
1730
1731    #[bench]
1732    fn bench_id_range_hit_1_percent_multi(bench: &mut Bencher) {
1733        let index = get_index_0_to_100();
1734        bench.iter(|| execute_query("ids", get_1_percent(), "", &index));
1735    }
1736
1737    #[bench]
1738    fn bench_id_range_hit_10_percent_intersect_with_10_percent_multi(bench: &mut Bencher) {
1739        let index = get_index_0_to_100();
1740        bench.iter(|| execute_query("ids", get_10_percent(), "AND id_name:few", &index));
1741    }
1742
1743    #[bench]
1744    fn bench_id_range_hit_1_percent_intersect_with_10_percent_multi(bench: &mut Bencher) {
1745        let index = get_index_0_to_100();
1746        bench.iter(|| execute_query("ids", get_1_percent(), "AND id_name:few", &index));
1747    }
1748
1749    #[bench]
1750    fn bench_id_range_hit_1_percent_intersect_with_90_percent_multi(bench: &mut Bencher) {
1751        let index = get_index_0_to_100();
1752        bench.iter(|| execute_query("ids", get_1_percent(), "AND id_name:many", &index));
1753    }
1754
1755    #[bench]
1756    fn bench_id_range_hit_1_percent_intersect_with_1_percent_multi(bench: &mut Bencher) {
1757        let index = get_index_0_to_100();
1758        bench.iter(|| execute_query("ids", get_1_percent(), "AND id_name:veryfew", &index));
1759    }
1760
1761    #[bench]
1762    fn bench_id_range_hit_10_percent_intersect_with_90_percent_multi(bench: &mut Bencher) {
1763        let index = get_index_0_to_100();
1764        bench.iter(|| execute_query("ids", get_10_percent(), "AND id_name:many", &index));
1765    }
1766
1767    #[bench]
1768    fn bench_id_range_hit_90_percent_intersect_with_90_percent_multi(bench: &mut Bencher) {
1769        let index = get_index_0_to_100();
1770        bench.iter(|| execute_query("ids", get_90_percent(), "AND id_name:many", &index));
1771    }
1772
1773    #[bench]
1774    fn bench_id_range_hit_90_percent_intersect_with_10_percent_multi(bench: &mut Bencher) {
1775        let index = get_index_0_to_100();
1776        bench.iter(|| execute_query("ids", get_90_percent(), "AND id_name:few", &index));
1777    }
1778
1779    #[bench]
1780    fn bench_id_range_hit_90_percent_intersect_with_1_percent_multi(bench: &mut Bencher) {
1781        let index = get_index_0_to_100();
1782        bench.iter(|| execute_query("ids", get_90_percent(), "AND id_name:veryfew", &index));
1783    }
1784}
1785
1786#[cfg(all(test, feature = "unstable"))]
1787mod bench_ip {
1788
1789    use rand::rngs::StdRng;
1790    use rand::{Rng, SeedableRng};
1791    use test::Bencher;
1792
1793    use super::ip_range_tests::*;
1794    use super::*;
1795    use crate::collector::Count;
1796    use crate::query::QueryParser;
1797    use crate::Index;
1798
1799    fn get_index_0_to_100() -> Index {
1800        let mut rng = StdRng::from_seed([1u8; 32]);
1801        let num_vals = 100_000;
1802        let docs: Vec<_> = (0..num_vals)
1803            .map(|_i| {
1804                let id = if rng.gen_bool(0.01) {
1805                    "veryfew".to_string() // 1%
1806                } else if rng.gen_bool(0.1) {
1807                    "few".to_string() // 9%
1808                } else {
1809                    "many".to_string() // 90%
1810                };
1811                Doc {
1812                    id,
1813                    // Multiply by 1000, so that we create many buckets in the compact space
1814                    // The benches depend on this range to select n-percent of elements with the
1815                    // methods below.
1816                    ip: Ipv6Addr::from_u128(rng.gen_range(0..100) * 1000),
1817                }
1818            })
1819            .collect();
1820
1821        create_index_from_ip_docs(&docs)
1822    }
1823
1824    fn get_90_percent() -> RangeInclusive<Ipv6Addr> {
1825        let start = Ipv6Addr::from_u128(0);
1826        let end = Ipv6Addr::from_u128(90 * 1000);
1827        start..=end
1828    }
1829
1830    fn get_10_percent() -> RangeInclusive<Ipv6Addr> {
1831        let start = Ipv6Addr::from_u128(0);
1832        let end = Ipv6Addr::from_u128(10 * 1000);
1833        start..=end
1834    }
1835
1836    fn get_1_percent() -> RangeInclusive<Ipv6Addr> {
1837        let start = Ipv6Addr::from_u128(10 * 1000);
1838        let end = Ipv6Addr::from_u128(10 * 1000);
1839        start..=end
1840    }
1841
1842    fn execute_query(
1843        field: &str,
1844        ip_range: RangeInclusive<Ipv6Addr>,
1845        suffix: &str,
1846        index: &Index,
1847    ) -> usize {
1848        let gen_query_inclusive = |from: &Ipv6Addr, to: &Ipv6Addr| {
1849            format!(
1850                "{}:[{} TO {}] {}",
1851                field,
1852                &from.to_string(),
1853                &to.to_string(),
1854                suffix
1855            )
1856        };
1857
1858        let query = gen_query_inclusive(ip_range.start(), ip_range.end());
1859        let query_from_text = |text: &str| {
1860            QueryParser::for_index(index, vec![])
1861                .parse_query(text)
1862                .unwrap()
1863        };
1864        let query = query_from_text(&query);
1865        let reader = index.reader().unwrap();
1866        let searcher = reader.searcher();
1867        searcher.search(&query, &(Count)).unwrap()
1868    }
1869
1870    #[bench]
1871    fn bench_ip_range_hit_90_percent(bench: &mut Bencher) {
1872        let index = get_index_0_to_100();
1873
1874        bench.iter(|| execute_query("ip", get_90_percent(), "", &index));
1875    }
1876
1877    #[bench]
1878    fn bench_ip_range_hit_10_percent(bench: &mut Bencher) {
1879        let index = get_index_0_to_100();
1880
1881        bench.iter(|| execute_query("ip", get_10_percent(), "", &index));
1882    }
1883
1884    #[bench]
1885    fn bench_ip_range_hit_1_percent(bench: &mut Bencher) {
1886        let index = get_index_0_to_100();
1887
1888        bench.iter(|| execute_query("ip", get_1_percent(), "", &index));
1889    }
1890
1891    #[bench]
1892    fn bench_ip_range_hit_10_percent_intersect_with_10_percent(bench: &mut Bencher) {
1893        let index = get_index_0_to_100();
1894
1895        bench.iter(|| execute_query("ip", get_10_percent(), "AND id:few", &index));
1896    }
1897
1898    #[bench]
1899    fn bench_ip_range_hit_1_percent_intersect_with_10_percent(bench: &mut Bencher) {
1900        let index = get_index_0_to_100();
1901
1902        bench.iter(|| execute_query("ip", get_1_percent(), "AND id:few", &index));
1903    }
1904
1905    #[bench]
1906    fn bench_ip_range_hit_1_percent_intersect_with_90_percent(bench: &mut Bencher) {
1907        let index = get_index_0_to_100();
1908
1909        bench.iter(|| execute_query("ip", get_1_percent(), "AND id:many", &index));
1910    }
1911
1912    #[bench]
1913    fn bench_ip_range_hit_1_percent_intersect_with_1_percent(bench: &mut Bencher) {
1914        let index = get_index_0_to_100();
1915
1916        bench.iter(|| execute_query("ip", get_1_percent(), "AND id:veryfew", &index));
1917    }
1918
1919    #[bench]
1920    fn bench_ip_range_hit_10_percent_intersect_with_90_percent(bench: &mut Bencher) {
1921        let index = get_index_0_to_100();
1922
1923        bench.iter(|| execute_query("ip", get_10_percent(), "AND id:many", &index));
1924    }
1925
1926    #[bench]
1927    fn bench_ip_range_hit_90_percent_intersect_with_90_percent(bench: &mut Bencher) {
1928        let index = get_index_0_to_100();
1929
1930        bench.iter(|| execute_query("ip", get_90_percent(), "AND id:many", &index));
1931    }
1932
1933    #[bench]
1934    fn bench_ip_range_hit_90_percent_intersect_with_10_percent(bench: &mut Bencher) {
1935        let index = get_index_0_to_100();
1936
1937        bench.iter(|| execute_query("ip", get_90_percent(), "AND id:few", &index));
1938    }
1939
1940    #[bench]
1941    fn bench_ip_range_hit_90_percent_intersect_with_1_percent(bench: &mut Bencher) {
1942        let index = get_index_0_to_100();
1943
1944        bench.iter(|| execute_query("ip", get_90_percent(), "AND id:veryfew", &index));
1945    }
1946
1947    #[bench]
1948    fn bench_ip_range_hit_90_percent_multi(bench: &mut Bencher) {
1949        let index = get_index_0_to_100();
1950
1951        bench.iter(|| execute_query("ips", get_90_percent(), "", &index));
1952    }
1953
1954    #[bench]
1955    fn bench_ip_range_hit_10_percent_multi(bench: &mut Bencher) {
1956        let index = get_index_0_to_100();
1957
1958        bench.iter(|| execute_query("ips", get_10_percent(), "", &index));
1959    }
1960
1961    #[bench]
1962    fn bench_ip_range_hit_1_percent_multi(bench: &mut Bencher) {
1963        let index = get_index_0_to_100();
1964
1965        bench.iter(|| execute_query("ips", get_1_percent(), "", &index));
1966    }
1967
1968    #[bench]
1969    fn bench_ip_range_hit_10_percent_intersect_with_10_percent_multi(bench: &mut Bencher) {
1970        let index = get_index_0_to_100();
1971
1972        bench.iter(|| execute_query("ips", get_10_percent(), "AND id:few", &index));
1973    }
1974
1975    #[bench]
1976    fn bench_ip_range_hit_1_percent_intersect_with_10_percent_multi(bench: &mut Bencher) {
1977        let index = get_index_0_to_100();
1978
1979        bench.iter(|| execute_query("ips", get_1_percent(), "AND id:few", &index));
1980    }
1981
1982    #[bench]
1983    fn bench_ip_range_hit_1_percent_intersect_with_90_percent_multi(bench: &mut Bencher) {
1984        let index = get_index_0_to_100();
1985        bench.iter(|| execute_query("ips", get_1_percent(), "AND id:many", &index));
1986    }
1987
1988    #[bench]
1989    fn bench_ip_range_hit_1_percent_intersect_with_1_percent_multi(bench: &mut Bencher) {
1990        let index = get_index_0_to_100();
1991
1992        bench.iter(|| execute_query("ips", get_1_percent(), "AND id:veryfew", &index));
1993    }
1994
1995    #[bench]
1996    fn bench_ip_range_hit_10_percent_intersect_with_90_percent_multi(bench: &mut Bencher) {
1997        let index = get_index_0_to_100();
1998
1999        bench.iter(|| execute_query("ips", get_10_percent(), "AND id:many", &index));
2000    }
2001
2002    #[bench]
2003    fn bench_ip_range_hit_90_percent_intersect_with_90_percent_multi(bench: &mut Bencher) {
2004        let index = get_index_0_to_100();
2005
2006        bench.iter(|| execute_query("ips", get_90_percent(), "AND id:many", &index));
2007    }
2008
2009    #[bench]
2010    fn bench_ip_range_hit_90_percent_intersect_with_10_percent_multi(bench: &mut Bencher) {
2011        let index = get_index_0_to_100();
2012
2013        bench.iter(|| execute_query("ips", get_90_percent(), "AND id:few", &index));
2014    }
2015
2016    #[bench]
2017    fn bench_ip_range_hit_90_percent_intersect_with_1_percent_multi(bench: &mut Bencher) {
2018        let index = get_index_0_to_100();
2019
2020        bench.iter(|| execute_query("ips", get_90_percent(), "AND id:veryfew", &index));
2021    }
2022}