@@ -76,12 +76,14 @@ impl Weight for FastFieldRangeWeight {
76
76
else {
77
77
return Ok ( Box :: new ( EmptyScorer ) ) ;
78
78
} ;
79
+ #[ allow( clippy:: reversed_empty_ranges) ]
79
80
let value_range = bound_to_value_range (
80
81
& self . lower_bound ,
81
82
& self . upper_bound ,
82
83
column. min_value ( ) ,
83
84
column. max_value ( ) ,
84
- ) ;
85
+ )
86
+ . unwrap_or ( 1 ..=0 ) ; // empty range
85
87
if value_range. is_empty ( ) {
86
88
return Ok ( Box :: new ( EmptyScorer ) ) ;
87
89
}
@@ -102,26 +104,28 @@ impl Weight for FastFieldRangeWeight {
102
104
}
103
105
}
104
106
107
+ // Returns None, if the range cannot be converted to a inclusive range (which equals to a empty
108
+ // range).
105
109
fn bound_to_value_range < T : MonotonicallyMappableToU64 > (
106
110
lower_bound : & Bound < T > ,
107
111
upper_bound : & Bound < T > ,
108
112
min_value : T ,
109
113
max_value : T ,
110
- ) -> RangeInclusive < T > {
114
+ ) -> Option < RangeInclusive < T > > {
111
115
let mut start_value = match lower_bound {
112
116
Bound :: Included ( val) => * val,
113
- Bound :: Excluded ( val) => T :: from_u64 ( val. to_u64 ( ) + 1 ) ,
117
+ Bound :: Excluded ( val) => T :: from_u64 ( val. to_u64 ( ) . checked_add ( 1 ) ? ) ,
114
118
Bound :: Unbounded => min_value,
115
119
} ;
116
120
if start_value. partial_cmp ( & min_value) == Some ( std:: cmp:: Ordering :: Less ) {
117
121
start_value = min_value;
118
122
}
119
123
let end_value = match upper_bound {
120
124
Bound :: Included ( val) => * val,
121
- Bound :: Excluded ( val) => T :: from_u64 ( val. to_u64 ( ) - 1 ) ,
125
+ Bound :: Excluded ( val) => T :: from_u64 ( val. to_u64 ( ) . checked_sub ( 1 ) ? ) ,
122
126
Bound :: Unbounded => max_value,
123
127
} ;
124
- start_value..=end_value
128
+ Some ( start_value..=end_value)
125
129
}
126
130
127
131
#[ cfg( test) ]
@@ -295,6 +299,9 @@ pub mod tests {
295
299
let gen_query_inclusive = |field : & str , range : RangeInclusive < u64 > | {
296
300
format ! ( "{}:[{} TO {}]" , field, range. start( ) , range. end( ) )
297
301
} ;
302
+ let gen_query_exclusive = |field : & str , range : RangeInclusive < u64 > | {
303
+ format ! ( "{}:{{{} TO {}}}" , field, range. start( ) , range. end( ) )
304
+ } ;
298
305
299
306
let test_sample = |sample_docs : Vec < Doc > | {
300
307
let mut ids: Vec < u64 > = sample_docs. iter ( ) . map ( |doc| doc. id ) . collect ( ) ;
@@ -310,6 +317,20 @@ pub mod tests {
310
317
let query = gen_query_inclusive ( "ids" , ids[ 0 ] ..=ids[ 1 ] ) ;
311
318
assert_eq ! ( get_num_hits( query_from_text( & query) ) , expected_num_hits) ;
312
319
320
+ // Exclusive range
321
+ let expected_num_hits = docs
322
+ . iter ( )
323
+ . filter ( |doc| {
324
+ ( ids[ 0 ] . saturating_add ( 1 ) ..=ids[ 1 ] . saturating_sub ( 1 ) ) . contains ( & doc. id )
325
+ } )
326
+ . count ( ) ;
327
+
328
+ let query = gen_query_exclusive ( "id" , ids[ 0 ] ..=ids[ 1 ] ) ;
329
+ assert_eq ! ( get_num_hits( query_from_text( & query) ) , expected_num_hits) ;
330
+
331
+ let query = gen_query_exclusive ( "ids" , ids[ 0 ] ..=ids[ 1 ] ) ;
332
+ assert_eq ! ( get_num_hits( query_from_text( & query) ) , expected_num_hits) ;
333
+
313
334
// Intersection search
314
335
let id_filter = sample_docs[ 0 ] . id_name . to_string ( ) ;
315
336
let expected_num_hits = docs
0 commit comments