1use 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)]
22pub struct FastFieldRangeQuery {
24 bounds: BoundsRange<Term>,
25}
26impl FastFieldRangeQuery {
27 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#[derive(Clone, Debug)]
43pub struct FastFieldRangeWeight {
44 bounds: BoundsRange<Term>,
45}
46
47impl FastFieldRangeWeight {
48 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 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 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 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 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
235fn 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 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 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 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 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 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); 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
415pub(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
444fn 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 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 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 }
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 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 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 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 assert_eq!(
848 count(RangeQuery::new(
849 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 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 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 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 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 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 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 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 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 {
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 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 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 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 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 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 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 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() } else if rng.gen_bool(0.1) {
1599 "few".to_string() } else {
1601 "many".to_string() };
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() } else if rng.gen_bool(0.1) {
1807 "few".to_string() } else {
1809 "many".to_string() };
1811 Doc {
1812 id,
1813 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}