Skip to content

Commit 563f4e4

Browse files
committed
handle exlusive out of bounds ranges on fastfield range queries
closes quickwit-oss/quickwit#3790
1 parent 1932513 commit 563f4e4

File tree

1 file changed

+25
-5
lines changed

1 file changed

+25
-5
lines changed

src/query/range_query/range_query_u64_fastfield.rs

+25-5
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ impl Weight for FastFieldRangeWeight {
8181
&self.upper_bound,
8282
column.min_value(),
8383
column.max_value(),
84-
);
84+
)
85+
.unwrap_or(1..=0); // empty range
8586
if value_range.is_empty() {
8687
return Ok(Box::new(EmptyScorer));
8788
}
@@ -102,26 +103,28 @@ impl Weight for FastFieldRangeWeight {
102103
}
103104
}
104105

106+
// Returns None, if the range cannot be converted to a inclusive range (which equals to a empty
107+
// range).
105108
fn bound_to_value_range<T: MonotonicallyMappableToU64>(
106109
lower_bound: &Bound<T>,
107110
upper_bound: &Bound<T>,
108111
min_value: T,
109112
max_value: T,
110-
) -> RangeInclusive<T> {
113+
) -> Option<RangeInclusive<T>> {
111114
let mut start_value = match lower_bound {
112115
Bound::Included(val) => *val,
113-
Bound::Excluded(val) => T::from_u64(val.to_u64() + 1),
116+
Bound::Excluded(val) => T::from_u64(val.to_u64().checked_add(1)?),
114117
Bound::Unbounded => min_value,
115118
};
116119
if start_value.partial_cmp(&min_value) == Some(std::cmp::Ordering::Less) {
117120
start_value = min_value;
118121
}
119122
let end_value = match upper_bound {
120123
Bound::Included(val) => *val,
121-
Bound::Excluded(val) => T::from_u64(val.to_u64() - 1),
124+
Bound::Excluded(val) => T::from_u64(val.to_u64().checked_sub(1)?),
122125
Bound::Unbounded => max_value,
123126
};
124-
start_value..=end_value
127+
Some(start_value..=end_value)
125128
}
126129

127130
#[cfg(test)]
@@ -295,6 +298,9 @@ pub mod tests {
295298
let gen_query_inclusive = |field: &str, range: RangeInclusive<u64>| {
296299
format!("{}:[{} TO {}]", field, range.start(), range.end())
297300
};
301+
let gen_query_exclusive = |field: &str, range: RangeInclusive<u64>| {
302+
format!("{}:{{{} TO {}}}", field, range.start(), range.end())
303+
};
298304

299305
let test_sample = |sample_docs: Vec<Doc>| {
300306
let mut ids: Vec<u64> = sample_docs.iter().map(|doc| doc.id).collect();
@@ -310,6 +316,20 @@ pub mod tests {
310316
let query = gen_query_inclusive("ids", ids[0]..=ids[1]);
311317
assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
312318

319+
// Exclusive range
320+
let expected_num_hits = docs
321+
.iter()
322+
.filter(|doc| {
323+
(ids[0].saturating_add(1)..=ids[1].saturating_sub(1)).contains(&doc.id)
324+
})
325+
.count();
326+
327+
let query = gen_query_exclusive("id", ids[0]..=ids[1]);
328+
assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
329+
330+
let query = gen_query_exclusive("ids", ids[0]..=ids[1]);
331+
assert_eq!(get_num_hits(query_from_text(&query)), expected_num_hits);
332+
313333
// Intersection search
314334
let id_filter = sample_docs[0].id_name.to_string();
315335
let expected_num_hits = docs

0 commit comments

Comments
 (0)