2022-09-01 11:40:29 +02:00
use std ::collections ::HashSet ;
use std ::fmt ::{ Debug , Display } ;
use std ::ops ::Bound ::{ self , Excluded , Included } ;
2021-01-07 10:17:27 +01:00
use either ::Either ;
2021-11-04 15:02:36 +01:00
pub use filter_parser ::{ Condition , Error as FPError , FilterCondition , Span , Token } ;
2020-11-26 20:42:54 +01:00
use roaring ::RoaringBitmap ;
2022-09-01 11:40:29 +02:00
use super ::facet_range_search ;
2021-09-16 11:56:18 +02:00
use crate ::error ::{ Error , UserError } ;
2022-09-05 17:31:26 +02:00
use crate ::heed_codec ::facet ::{
FacetGroupKey , FacetGroupKeyCodec , FacetGroupValueCodec , OrderedF64Codec ,
} ;
2022-08-31 08:27:16 +02:00
use crate ::{ distance_between_two_points , lat_lng_to_xyz , FieldId , Index , Result } ;
2021-12-07 16:32:48 +01:00
/// The maximum number of filters the filter AST can process.
2021-12-07 17:36:45 +01:00
const MAX_FILTER_DEPTH : usize = 2000 ;
2021-12-07 16:32:48 +01:00
2021-11-06 01:32:12 +01:00
#[ derive(Debug, Clone, PartialEq, Eq) ]
2021-10-22 14:33:18 +02:00
pub struct Filter < ' a > {
condition : FilterCondition < ' a > ,
2020-11-26 20:42:54 +01:00
}
2021-11-04 17:24:55 +01:00
#[ derive(Debug) ]
2023-02-14 00:14:17 +01:00
pub enum BadGeoError {
Lat ( f64 ) ,
Lng ( f64 ) ,
BoundingBoxTopIsBelowBottom ( f64 , f64 ) ,
2023-02-07 18:57:27 +01:00
}
2023-02-14 00:14:17 +01:00
impl std ::error ::Error for BadGeoError { }
2023-02-07 18:57:27 +01:00
2023-02-14 00:14:17 +01:00
impl Display for BadGeoError {
2023-02-07 18:57:27 +01:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
2023-02-14 00:14:17 +01:00
Self ::BoundingBoxTopIsBelowBottom ( top , bottom ) = > {
2023-02-09 17:37:18 +01:00
write! ( f , " The top latitude `{top}` is below the bottom latitude `{bottom}`. " )
}
2023-02-14 00:14:17 +01:00
Self ::Lat ( lat ) = > write! (
2023-02-09 17:37:18 +01:00
f ,
" Bad latitude `{}`. Latitude must be contained between -90 and 90 degrees. " ,
lat
) ,
2023-02-14 00:14:17 +01:00
Self ::Lng ( lng ) = > write! (
2023-02-09 17:37:18 +01:00
f ,
" Bad longitude `{}`. Longitude must be contained between -180 and 180 degrees. " ,
lng
) ,
2023-02-07 18:57:27 +01:00
}
}
}
#[ derive(Debug) ]
enum FilterError < ' a > {
AttributeNotFilterable { attribute : & ' a str , filterable_fields : HashSet < String > } ,
2023-02-14 00:14:17 +01:00
ParseGeoError ( BadGeoError ) ,
2023-02-09 17:37:18 +01:00
ReservedGeo ( & ' a str ) ,
2021-11-04 17:24:55 +01:00
Reserved ( & ' a str ) ,
2021-12-07 16:32:48 +01:00
TooDeep ,
2021-11-04 17:24:55 +01:00
}
impl < ' a > std ::error ::Error for FilterError < ' a > { }
2023-02-14 00:14:17 +01:00
impl < ' a > From < BadGeoError > for FilterError < ' a > {
fn from ( geo_error : BadGeoError ) -> Self {
2023-02-09 16:35:15 +01:00
FilterError ::ParseGeoError ( geo_error )
}
}
2021-11-04 17:24:55 +01:00
impl < ' a > Display for FilterError < ' a > {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
2022-03-15 22:36:10 +01:00
Self ::AttributeNotFilterable { attribute , filterable_fields } = > {
if filterable_fields . is_empty ( ) {
2022-03-15 22:12:51 +01:00
write! (
f ,
" Attribute `{}` is not filterable. This index does not have configured filterable attributes. " ,
attribute ,
)
} else {
2023-02-07 18:57:27 +01:00
let filterables_list = filterable_fields
. iter ( )
. map ( AsRef ::as_ref )
. collect ::< Vec < & str > > ( )
. join ( " " ) ;
2022-03-15 22:36:10 +01:00
2022-03-15 22:12:51 +01:00
write! (
f ,
" Attribute `{}` is not filterable. Available filterable attributes are: `{}`. " ,
attribute ,
2022-03-15 22:36:10 +01:00
filterables_list ,
2022-03-15 22:12:51 +01:00
)
}
2023-02-07 18:57:27 +01:00
}
Self ::TooDeep = > write! (
f ,
2021-12-07 16:32:48 +01:00
" Too many filter conditions, can't process more than {} filters. " ,
MAX_FILTER_DEPTH
) ,
2023-02-09 17:37:18 +01:00
Self ::ReservedGeo ( keyword ) = > write! ( f , " `{}` is a reserved keyword and thus can't be used as a filter expression. Use the `_geoRadius(latitude, longitude, distance)` or `_geoBoundingBox([latitude, longitude], [latitude, longitude])` built-in rules to filter on `_geo` field coordinates. " , keyword ) ,
2021-11-04 17:24:55 +01:00
Self ::Reserved ( keyword ) = > write! (
f ,
" `{}` is a reserved keyword and thus can't be used as a filter expression. " ,
keyword
) ,
2023-02-07 18:57:27 +01:00
Self ::ParseGeoError ( error ) = > write! ( f , " {} " , error ) ,
2021-11-04 17:24:55 +01:00
}
}
}
impl < ' a > From < FPError < ' a > > for Error {
fn from ( error : FPError < ' a > ) -> Self {
Self ::UserError ( UserError ::InvalidFilter ( error . to_string ( ) ) )
}
}
2021-11-04 15:52:22 +01:00
impl < ' a > From < Filter < ' a > > for FilterCondition < ' a > {
fn from ( f : Filter < ' a > ) -> Self {
f . condition
}
}
2021-10-22 14:33:18 +02:00
impl < ' a > Filter < ' a > {
2021-10-22 16:38:35 +02:00
pub fn from_array < I , J > ( array : I ) -> Result < Option < Self > >
2021-06-16 18:33:33 +02:00
where
2021-10-22 14:33:18 +02:00
I : IntoIterator < Item = Either < J , & ' a str > > ,
J : IntoIterator < Item = & ' a str > ,
2021-01-07 10:17:27 +01:00
{
2022-06-20 18:46:57 +02:00
let mut ands = vec! [ ] ;
2021-01-07 10:17:27 +01:00
for either in array {
match either {
Either ::Left ( array ) = > {
2022-06-20 18:46:57 +02:00
let mut ors = vec! [ ] ;
2021-01-07 10:17:27 +01:00
for rule in array {
2022-10-24 21:34:13 +02:00
if let Some ( filter ) = Self ::from_str ( rule ) ? {
2022-06-20 18:46:57 +02:00
ors . push ( filter . condition ) ;
2021-12-09 11:13:12 +01:00
}
2021-01-07 10:17:27 +01:00
}
2022-10-14 16:44:10 +02:00
match ors . len ( ) {
2022-11-03 09:34:33 +01:00
0 = > ( ) ,
2022-10-14 16:44:10 +02:00
1 = > ands . push ( ors . pop ( ) . unwrap ( ) ) ,
2022-11-03 09:34:33 +01:00
_ = > ands . push ( FilterCondition ::Or ( ors ) ) ,
2021-01-07 10:17:27 +01:00
}
2021-06-16 18:33:33 +02:00
}
2021-01-07 10:17:27 +01:00
Either ::Right ( rule ) = > {
2022-10-24 21:34:13 +02:00
if let Some ( filter ) = Self ::from_str ( rule ) ? {
2022-06-20 18:46:57 +02:00
ands . push ( filter . condition ) ;
2021-12-09 11:13:12 +01:00
}
2021-01-07 10:17:27 +01:00
}
}
}
2022-06-20 18:46:57 +02:00
let and = if ands . is_empty ( ) {
return Ok ( None ) ;
} else if ands . len ( ) = = 1 {
2022-08-18 13:06:57 +02:00
ands . pop ( ) . unwrap ( )
2022-06-20 18:46:57 +02:00
} else {
FilterCondition ::And ( ands )
} ;
2021-01-07 10:17:27 +01:00
2022-06-20 18:46:57 +02:00
if let Some ( token ) = and . token_at_depth ( MAX_FILTER_DEPTH ) {
2021-12-07 16:32:48 +01:00
return Err ( token . as_external_error ( FilterError ::TooDeep ) . into ( ) ) ;
}
2022-06-20 18:46:57 +02:00
Ok ( Some ( Self { condition : and } ) )
2021-01-07 10:17:27 +01:00
}
2021-10-14 15:37:59 +02:00
2022-10-14 16:44:10 +02:00
#[ allow(clippy::should_implement_trait) ]
2021-12-09 11:13:12 +01:00
pub fn from_str ( expression : & ' a str ) -> Result < Option < Self > > {
2021-11-04 15:02:36 +01:00
let condition = match FilterCondition ::parse ( expression ) {
2021-12-09 11:13:12 +01:00
Ok ( Some ( fc ) ) = > Ok ( fc ) ,
Ok ( None ) = > return Ok ( None ) ,
2021-11-04 17:24:55 +01:00
Err ( e ) = > Err ( Error ::UserError ( UserError ::InvalidFilter ( e . to_string ( ) ) ) ) ,
2021-10-22 16:38:35 +02:00
} ? ;
2021-12-07 17:20:11 +01:00
if let Some ( token ) = condition . token_at_depth ( MAX_FILTER_DEPTH ) {
return Err ( token . as_external_error ( FilterError ::TooDeep ) . into ( ) ) ;
}
2021-12-09 11:13:12 +01:00
Ok ( Some ( Self { condition } ) )
2020-11-26 20:42:54 +01:00
}
}
2021-10-22 14:33:18 +02:00
impl < ' a > Filter < ' a > {
2022-08-31 08:27:16 +02:00
pub fn evaluate ( & self , rtxn : & heed ::RoTxn , index : & Index ) -> Result < RoaringBitmap > {
// to avoid doing this for each recursive call we're going to do it ONCE ahead of time
let soft_deleted_documents = index . soft_deleted_documents_ids ( rtxn ) ? ;
let filterable_fields = index . filterable_fields ( rtxn ) ? ;
2022-08-29 16:01:54 +02:00
2022-08-31 08:27:16 +02:00
// and finally we delete all the soft_deleted_documents, again, only once at the very end
self . inner_evaluate ( rtxn , index , & filterable_fields )
. map ( | result | result - soft_deleted_documents )
2020-11-26 20:42:54 +01:00
}
2021-05-03 11:45:45 +02:00
fn evaluate_operator (
2021-04-07 11:57:16 +02:00
rtxn : & heed ::RoTxn ,
2020-11-26 20:42:54 +01:00
index : & Index ,
field_id : FieldId ,
2021-10-22 14:33:18 +02:00
operator : & Condition < ' a > ,
2021-06-16 18:33:33 +02:00
) -> Result < RoaringBitmap > {
2022-06-13 17:59:34 +02:00
let numbers_db = index . facet_id_f64_docids ;
let strings_db = index . facet_id_string_docids ;
2020-11-26 20:42:54 +01:00
// Make sure we always bound the ranges with the field id and the level,
// as the facets values are all in the same database and prefixed by the
// field id and the level.
2021-10-22 17:23:22 +02:00
2020-11-26 20:42:54 +01:00
let ( left , right ) = match operator {
2022-11-08 10:17:16 +01:00
Condition ::GreaterThan ( val ) = > {
( Excluded ( val . parse_finite_float ( ) ? ) , Included ( f64 ::MAX ) )
}
Condition ::GreaterThanOrEqual ( val ) = > {
( Included ( val . parse_finite_float ( ) ? ) , Included ( f64 ::MAX ) )
}
Condition ::LowerThan ( val ) = > ( Included ( f64 ::MIN ) , Excluded ( val . parse_finite_float ( ) ? ) ) ,
Condition ::LowerThanOrEqual ( val ) = > {
( Included ( f64 ::MIN ) , Included ( val . parse_finite_float ( ) ? ) )
}
Condition ::Between { from , to } = > {
( Included ( from . parse_finite_float ( ) ? ) , Included ( to . parse_finite_float ( ) ? ) )
}
2022-06-14 16:42:09 +02:00
Condition ::Exists = > {
2022-05-25 11:55:16 +02:00
let exist = index . exists_faceted_documents_ids ( rtxn , field_id ) ? ;
return Ok ( exist ) ;
}
2021-10-22 14:33:18 +02:00
Condition ::Equal ( val ) = > {
2022-08-29 16:01:54 +02:00
let string_docids = strings_db
. get (
rtxn ,
2022-09-05 13:01:36 +02:00
& FacetGroupKey {
field_id ,
level : 0 ,
left_bound : & val . value ( ) . to_lowercase ( ) ,
} ,
2022-08-29 16:01:54 +02:00
) ?
. map ( | v | v . bitmap )
2022-06-09 16:03:49 +02:00
. unwrap_or_default ( ) ;
2022-11-08 10:17:16 +01:00
let number = val . parse_finite_float ( ) . ok ( ) ;
2021-05-03 11:45:45 +02:00
let number_docids = match number {
2022-12-07 12:19:32 +01:00
Some ( n ) = > numbers_db
. get ( rtxn , & FacetGroupKey { field_id , level : 0 , left_bound : n } ) ?
. map ( | v | v . bitmap )
. unwrap_or_default ( ) ,
2021-05-03 11:45:45 +02:00
None = > RoaringBitmap ::new ( ) ,
} ;
return Ok ( string_docids | number_docids ) ;
2021-06-16 18:33:33 +02:00
}
2021-10-22 14:33:18 +02:00
Condition ::NotEqual ( val ) = > {
let operator = Condition ::Equal ( val . clone ( ) ) ;
2022-07-18 17:09:52 +02:00
let docids = Self ::evaluate_operator ( rtxn , index , field_id , & operator ) ? ;
2022-06-14 15:08:40 +02:00
let all_ids = index . documents_ids ( rtxn ) ? ;
return Ok ( all_ids - docids ) ;
2021-10-22 18:03:39 +02:00
}
2020-11-26 20:42:54 +01:00
} ;
2022-12-07 12:19:32 +01:00
let mut output = RoaringBitmap ::new ( ) ;
Self ::explore_facet_number_levels ( rtxn , numbers_db , field_id , left , right , & mut output ) ? ;
Ok ( output )
2020-11-26 20:42:54 +01:00
}
2022-08-31 08:27:16 +02:00
/// Aggregates the documents ids that are part of the specified range automatically
/// going deeper through the levels.
fn explore_facet_number_levels (
rtxn : & heed ::RoTxn ,
2022-09-05 13:01:36 +02:00
db : heed ::Database < FacetGroupKeyCodec < OrderedF64Codec > , FacetGroupValueCodec > ,
2022-08-31 08:27:16 +02:00
field_id : FieldId ,
left : Bound < f64 > ,
right : Bound < f64 > ,
output : & mut RoaringBitmap ,
) -> Result < ( ) > {
match ( left , right ) {
// lower TO upper when lower > upper must return no result
( Included ( l ) , Included ( r ) ) if l > r = > return Ok ( ( ) ) ,
( Included ( l ) , Excluded ( r ) ) if l > = r = > return Ok ( ( ) ) ,
( Excluded ( l ) , Excluded ( r ) ) if l > = r = > return Ok ( ( ) ) ,
( Excluded ( l ) , Included ( r ) ) if l > = r = > return Ok ( ( ) ) ,
( _ , _ ) = > ( ) ,
}
2022-09-07 17:56:38 +02:00
facet_range_search ::find_docids_of_facet_within_bounds ::< OrderedF64Codec > (
rtxn , db , field_id , & left , & right , output ,
2022-08-31 08:27:16 +02:00
) ? ;
2021-05-03 11:45:45 +02:00
2022-08-31 08:27:16 +02:00
Ok ( ( ) )
2022-06-13 17:59:34 +02:00
}
fn inner_evaluate (
& self ,
rtxn : & heed ::RoTxn ,
index : & Index ,
filterable_fields : & HashSet < String > ,
) -> Result < RoaringBitmap > {
2021-10-22 14:33:18 +02:00
match & self . condition {
2022-06-14 15:15:05 +02:00
FilterCondition ::Not ( f ) = > {
let all_ids = index . documents_ids ( rtxn ) ? ;
let selected = Self ::inner_evaluate (
& ( f . as_ref ( ) . clone ( ) ) . into ( ) ,
rtxn ,
index ,
filterable_fields ,
) ? ;
2022-10-24 21:34:13 +02:00
Ok ( all_ids - selected )
2022-06-14 15:15:05 +02:00
}
2022-05-30 13:58:11 +02:00
FilterCondition ::In { fid , els } = > {
2022-08-18 10:59:21 +02:00
if crate ::is_faceted ( fid . value ( ) , filterable_fields ) {
2022-05-30 13:58:11 +02:00
let field_ids_map = index . fields_ids_map ( rtxn ) ? ;
if let Some ( fid ) = field_ids_map . id ( fid . value ( ) ) {
let mut bitmap = RoaringBitmap ::new ( ) ;
for el in els {
let op = Condition ::Equal ( el . clone ( ) ) ;
let el_bitmap = Self ::evaluate_operator ( rtxn , index , fid , & op ) ? ;
bitmap | = el_bitmap ;
}
Ok ( bitmap )
} else {
Ok ( RoaringBitmap ::new ( ) )
}
} else {
2022-10-24 21:34:13 +02:00
Err ( fid . as_external_error ( FilterError ::AttributeNotFilterable {
2022-05-30 13:58:11 +02:00
attribute : fid . value ( ) ,
2022-08-18 10:59:21 +02:00
filterable_fields : filterable_fields . clone ( ) ,
2022-10-24 21:34:13 +02:00
} ) ) ?
2022-05-30 13:58:11 +02:00
}
}
2021-10-22 14:33:18 +02:00
FilterCondition ::Condition { fid , op } = > {
2022-06-13 17:59:34 +02:00
if crate ::is_faceted ( fid . value ( ) , filterable_fields ) {
2021-11-07 01:52:19 +01:00
let field_ids_map = index . fields_ids_map ( rtxn ) ? ;
2022-01-17 13:51:46 +01:00
if let Some ( fid ) = field_ids_map . id ( fid . value ( ) ) {
2022-10-24 21:34:13 +02:00
Self ::evaluate_operator ( rtxn , index , fid , op )
2021-11-07 01:52:19 +01:00
} else {
2022-10-24 21:34:13 +02:00
Ok ( RoaringBitmap ::new ( ) )
2021-11-07 01:52:19 +01:00
}
2021-10-22 17:23:22 +02:00
} else {
2023-02-06 11:36:49 +01:00
match fid . value ( ) {
2023-02-09 16:35:15 +01:00
attribute @ " _geo " = > {
2023-02-09 17:37:18 +01:00
Err ( fid . as_external_error ( FilterError ::ReservedGeo ( attribute ) ) ) ?
2023-02-09 16:35:15 +01:00
}
attribute if attribute . starts_with ( " _geoPoint( " ) = > {
2023-02-09 17:37:18 +01:00
Err ( fid . as_external_error ( FilterError ::ReservedGeo ( " _geoPoint " ) ) ) ?
2023-02-09 16:35:15 +01:00
}
2021-11-04 17:24:55 +01:00
attribute @ " _geoDistance " = > {
2022-10-24 21:34:13 +02:00
Err ( fid . as_external_error ( FilterError ::Reserved ( attribute ) ) ) ?
2021-10-22 19:00:33 +02:00
}
2021-11-04 17:24:55 +01:00
attribute = > {
2022-10-24 21:34:13 +02:00
Err ( fid . as_external_error ( FilterError ::AttributeNotFilterable {
attribute ,
filterable_fields : filterable_fields . clone ( ) ,
} ) ) ?
2021-10-22 19:00:33 +02:00
}
}
2021-10-22 17:23:22 +02:00
}
2021-06-16 18:33:33 +02:00
}
2022-06-20 18:46:57 +02:00
FilterCondition ::Or ( subfilters ) = > {
let mut bitmap = RoaringBitmap ::new ( ) ;
for f in subfilters {
2022-07-18 17:09:52 +02:00
bitmap | =
Self ::inner_evaluate ( & ( f . clone ( ) ) . into ( ) , rtxn , index , filterable_fields ) ? ;
2022-06-20 18:46:57 +02:00
}
Ok ( bitmap )
2021-06-16 18:33:33 +02:00
}
2022-06-20 18:46:57 +02:00
FilterCondition ::And ( subfilters ) = > {
let mut subfilters_iter = subfilters . iter ( ) ;
if let Some ( first_subfilter ) = subfilters_iter . next ( ) {
2022-07-18 17:09:52 +02:00
let mut bitmap = Self ::inner_evaluate (
& ( first_subfilter . clone ( ) ) . into ( ) ,
rtxn ,
index ,
filterable_fields ,
) ? ;
2022-06-20 18:46:57 +02:00
for f in subfilters_iter {
if bitmap . is_empty ( ) {
return Ok ( bitmap ) ;
}
2022-07-18 17:09:52 +02:00
bitmap & = Self ::inner_evaluate (
& ( f . clone ( ) ) . into ( ) ,
rtxn ,
index ,
filterable_fields ,
) ? ;
2022-06-20 18:46:57 +02:00
}
Ok ( bitmap )
} else {
Ok ( RoaringBitmap ::new ( ) )
2022-06-13 17:59:34 +02:00
}
2021-06-16 18:33:33 +02:00
}
2021-10-22 18:03:39 +02:00
FilterCondition ::GeoLowerThan { point , radius } = > {
2021-11-07 01:52:19 +01:00
if filterable_fields . contains ( " _geo " ) {
2022-11-08 10:17:16 +01:00
let base_point : [ f64 ; 2 ] =
[ point [ 0 ] . parse_finite_float ( ) ? , point [ 1 ] . parse_finite_float ( ) ? ] ;
2021-11-04 17:42:06 +01:00
if ! ( - 90. 0 ..= 90.0 ) . contains ( & base_point [ 0 ] ) {
2023-02-14 17:03:44 +01:00
return Err ( point [ 0 ] . as_external_error ( BadGeoError ::Lat ( base_point [ 0 ] ) ) ) ? ;
2021-11-04 17:42:06 +01:00
}
if ! ( - 180. 0 ..= 180.0 ) . contains ( & base_point [ 1 ] ) {
2023-02-14 17:03:44 +01:00
return Err ( point [ 1 ] . as_external_error ( BadGeoError ::Lng ( base_point [ 1 ] ) ) ) ? ;
2021-11-04 17:42:06 +01:00
}
2022-11-08 10:17:16 +01:00
let radius = radius . parse_finite_float ( ) ? ;
2021-10-22 18:08:18 +02:00
let rtree = match index . geo_rtree ( rtxn ) ? {
Some ( rtree ) = > rtree ,
None = > return Ok ( RoaringBitmap ::new ( ) ) ,
} ;
2021-10-22 18:03:39 +02:00
2021-12-14 12:21:24 +01:00
let xyz_base_point = lat_lng_to_xyz ( & base_point ) ;
2021-10-22 18:08:18 +02:00
let result = rtree
2021-12-14 12:21:24 +01:00
. nearest_neighbor_iter ( & xyz_base_point )
2021-10-22 18:08:18 +02:00
. take_while ( | point | {
2022-11-30 19:44:26 +01:00
distance_between_two_points ( & base_point , & point . data . 1 )
< = radius + f64 ::EPSILON
2021-10-22 18:08:18 +02:00
} )
2021-12-14 12:21:24 +01:00
. map ( | point | point . data . 0 )
2021-10-22 18:08:18 +02:00
. collect ( ) ;
2021-10-22 18:03:39 +02:00
2021-10-22 18:08:18 +02:00
Ok ( result )
} else {
2022-10-24 21:34:13 +02:00
Err ( point [ 0 ] . as_external_error ( FilterError ::AttributeNotFilterable {
2021-11-04 17:42:06 +01:00
attribute : " _geo " ,
2022-06-13 17:59:34 +02:00
filterable_fields : filterable_fields . clone ( ) ,
2022-10-24 21:34:13 +02:00
} ) ) ?
2021-10-22 18:08:18 +02:00
}
2021-10-22 18:03:39 +02:00
}
2022-10-28 15:30:53 +02:00
FilterCondition ::GeoBoundingBox { top_left_point , bottom_right_point } = > {
if filterable_fields . contains ( " _geo " ) {
let top_left : [ f64 ; 2 ] = [
top_left_point [ 0 ] . parse_finite_float ( ) ? ,
top_left_point [ 1 ] . parse_finite_float ( ) ? ,
] ;
let bottom_right : [ f64 ; 2 ] = [
bottom_right_point [ 0 ] . parse_finite_float ( ) ? ,
bottom_right_point [ 1 ] . parse_finite_float ( ) ? ,
] ;
if ! ( - 90. 0 ..= 90.0 ) . contains ( & top_left [ 0 ] ) {
2023-02-14 17:03:44 +01:00
return Err (
top_left_point [ 0 ] . as_external_error ( BadGeoError ::Lat ( top_left [ 0 ] ) )
) ? ;
2022-10-28 15:30:53 +02:00
}
if ! ( - 180. 0 ..= 180.0 ) . contains ( & top_left [ 1 ] ) {
2023-02-14 17:03:44 +01:00
return Err (
top_left_point [ 1 ] . as_external_error ( BadGeoError ::Lng ( top_left [ 1 ] ) )
) ? ;
2022-10-28 15:30:53 +02:00
}
if ! ( - 90. 0 ..= 90.0 ) . contains ( & bottom_right [ 0 ] ) {
2023-02-09 16:35:15 +01:00
return Err ( bottom_right_point [ 0 ]
2023-02-14 00:14:17 +01:00
. as_external_error ( BadGeoError ::Lat ( bottom_right [ 0 ] ) ) ) ? ;
2022-10-28 15:30:53 +02:00
}
if ! ( - 180. 0 ..= 180.0 ) . contains ( & bottom_right [ 1 ] ) {
2023-02-09 16:35:15 +01:00
return Err ( bottom_right_point [ 1 ]
2023-02-14 00:14:17 +01:00
. as_external_error ( BadGeoError ::Lng ( bottom_right [ 1 ] ) ) ) ? ;
2022-10-28 15:30:53 +02:00
}
2023-02-06 17:50:47 +01:00
if top_left [ 0 ] < bottom_right [ 0 ] {
return Err ( bottom_right_point [ 1 ] . as_external_error (
2023-02-14 17:03:44 +01:00
BadGeoError ::BoundingBoxTopIsBelowBottom ( top_left [ 0 ] , bottom_right [ 0 ] ) ,
2023-02-06 17:50:47 +01:00
) ) ? ;
}
2022-10-28 15:30:53 +02:00
2023-02-06 11:36:49 +01:00
// Instead of writing a custom `GeoBoundingBox` filter we're simply going to re-use the range
// filter to create the following filter;
// `_geo.lat {top_left[0]} TO {bottom_right[0]} AND _geo.lng {top_left[1]} TO {bottom_right[1]}`
2023-02-06 13:29:37 +01:00
// As we can see, we need to use a bunch of tokens that don't exist in the original filter,
// thus we're going to create tokens that point to a random span but contain our text.
2023-02-06 11:36:49 +01:00
2022-10-28 15:30:53 +02:00
let geo_lat_token =
2023-02-02 15:03:34 +01:00
Token ::new ( top_left_point [ 0 ] . original_span ( ) , Some ( " _geo.lat " . to_string ( ) ) ) ;
2022-10-28 15:30:53 +02:00
let condition_lat = FilterCondition ::Condition {
fid : geo_lat_token ,
op : Condition ::Between {
from : bottom_right_point [ 0 ] . clone ( ) ,
to : top_left_point [ 0 ] . clone ( ) ,
} ,
} ;
let selected_lat = Filter { condition : condition_lat } . inner_evaluate (
rtxn ,
index ,
filterable_fields ,
) ? ;
let geo_lng_token =
2023-02-02 15:03:34 +01:00
Token ::new ( top_left_point [ 1 ] . original_span ( ) , Some ( " _geo.lng " . to_string ( ) ) ) ;
2022-10-28 15:30:53 +02:00
let selected_lng = if top_left [ 1 ] > bottom_right [ 1 ] {
2023-02-06 11:36:49 +01:00
// In this case the bounding box is wrapping around the earth (going from 180 to -180).
// We need to update the lng part of the filter from;
// `_geo.lng {top_left[1]} TO {bottom_right[1]}` to
// `_geo.lng {top_left[1]} TO 180 AND _geo.lng -180 TO {bottom_right[1]}`
2023-02-02 18:19:56 +01:00
let min_lng_token = Token ::new (
top_left_point [ 1 ] . original_span ( ) ,
Some ( " -180.0 " . to_string ( ) ) ,
) ;
let max_lng_token = Token ::new (
top_left_point [ 1 ] . original_span ( ) ,
Some ( " 180.0 " . to_string ( ) ) ,
) ;
2022-10-28 15:30:53 +02:00
let condition_left = FilterCondition ::Condition {
fid : geo_lng_token . clone ( ) ,
op : Condition ::Between {
2022-10-28 19:01:23 +02:00
from : top_left_point [ 1 ] . clone ( ) ,
2022-10-28 15:30:53 +02:00
to : max_lng_token ,
} ,
} ;
let left = Filter { condition : condition_left } . inner_evaluate (
rtxn ,
index ,
filterable_fields ,
) ? ;
let condition_right = FilterCondition ::Condition {
fid : geo_lng_token ,
op : Condition ::Between {
2022-10-28 19:01:23 +02:00
from : min_lng_token ,
to : bottom_right_point [ 1 ] . clone ( ) ,
2022-10-28 15:30:53 +02:00
} ,
} ;
let right = Filter { condition : condition_right } . inner_evaluate (
rtxn ,
index ,
filterable_fields ,
) ? ;
2022-10-28 19:01:23 +02:00
left | right
2022-10-28 15:30:53 +02:00
} else {
let condition_lng = FilterCondition ::Condition {
fid : geo_lng_token ,
op : Condition ::Between {
from : top_left_point [ 1 ] . clone ( ) ,
to : bottom_right_point [ 1 ] . clone ( ) ,
} ,
} ;
Filter { condition : condition_lng } . inner_evaluate (
rtxn ,
index ,
filterable_fields ,
) ?
} ;
Ok ( selected_lat & selected_lng )
} else {
Err ( top_left_point [ 0 ] . as_external_error ( FilterError ::AttributeNotFilterable {
attribute : " _geo " ,
filterable_fields : filterable_fields . clone ( ) ,
} ) ) ?
}
}
2020-11-26 20:42:54 +01:00
}
}
}
2021-10-22 14:33:18 +02:00
impl < ' a > From < FilterCondition < ' a > > for Filter < ' a > {
fn from ( fc : FilterCondition < ' a > ) -> Self {
Self { condition : fc }
}
}
#[ cfg(test) ]
mod tests {
2022-08-04 11:34:10 +02:00
use std ::fmt ::Write ;
2022-12-07 14:11:20 +01:00
use std ::iter ::FromIterator ;
2022-08-04 11:34:10 +02:00
2021-10-22 14:33:18 +02:00
use big_s ::S ;
use either ::Either ;
use maplit ::hashset ;
2022-12-07 14:11:20 +01:00
use roaring ::RoaringBitmap ;
2022-08-04 11:34:10 +02:00
use crate ::index ::tests ::TempIndex ;
use crate ::Filter ;
2021-10-22 14:33:18 +02:00
2022-01-18 11:40:30 +01:00
#[ test ]
fn empty_db ( ) {
2022-08-04 10:46:10 +02:00
let index = TempIndex ::new ( ) ;
2022-11-30 19:44:26 +01:00
//Set the filterable fields to be the channel.
2022-08-04 10:46:10 +02:00
index
. update_settings ( | settings | {
settings . set_filterable_fields ( hashset! { S ( " PrIcE " ) } ) ;
} )
. unwrap ( ) ;
2022-01-18 11:40:30 +01:00
let rtxn = index . read_txn ( ) . unwrap ( ) ;
let filter = Filter ::from_str ( " PrIcE < 1000 " ) . unwrap ( ) . unwrap ( ) ;
let bitmap = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert! ( bitmap . is_empty ( ) ) ;
let filter = Filter ::from_str ( " NOT PrIcE >= 1000 " ) . unwrap ( ) . unwrap ( ) ;
let bitmap = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert! ( bitmap . is_empty ( ) ) ;
}
2021-10-22 14:33:18 +02:00
#[ test ]
fn from_array ( ) {
// Simple array with Left
2021-11-06 01:32:12 +01:00
let condition = Filter ::from_array ( vec! [ Either ::Left ( [ " channel = mv " ] ) ] ) . unwrap ( ) . unwrap ( ) ;
2021-12-09 11:13:12 +01:00
let expected = Filter ::from_str ( " channel = mv " ) . unwrap ( ) . unwrap ( ) ;
2021-10-22 14:33:18 +02:00
assert_eq! ( condition , expected ) ;
// Simple array with Right
2021-11-06 01:32:12 +01:00
let condition = Filter ::from_array ::< _ , Option < & str > > ( vec! [ Either ::Right ( " channel = mv " ) ] )
. unwrap ( )
. unwrap ( ) ;
2021-12-09 11:13:12 +01:00
let expected = Filter ::from_str ( " channel = mv " ) . unwrap ( ) . unwrap ( ) ;
2021-10-22 14:33:18 +02:00
assert_eq! ( condition , expected ) ;
// Array with Left and escaped quote
2021-11-06 01:32:12 +01:00
let condition =
Filter ::from_array ( vec! [ Either ::Left ( [ " channel = \" Mister Mv \" " ] ) ] ) . unwrap ( ) . unwrap ( ) ;
2021-12-09 11:13:12 +01:00
let expected = Filter ::from_str ( " channel = \" Mister Mv \" " ) . unwrap ( ) . unwrap ( ) ;
2021-10-22 14:33:18 +02:00
assert_eq! ( condition , expected ) ;
// Array with Right and escaped quote
2021-11-06 01:32:12 +01:00
let condition =
Filter ::from_array ::< _ , Option < & str > > ( vec! [ Either ::Right ( " channel = \" Mister Mv \" " ) ] )
. unwrap ( )
. unwrap ( ) ;
2021-12-09 11:13:12 +01:00
let expected = Filter ::from_str ( " channel = \" Mister Mv \" " ) . unwrap ( ) . unwrap ( ) ;
2021-10-22 14:33:18 +02:00
assert_eq! ( condition , expected ) ;
// Array with Left and escaped simple quote
2021-11-06 01:32:12 +01:00
let condition =
Filter ::from_array ( vec! [ Either ::Left ( [ " channel = 'Mister Mv' " ] ) ] ) . unwrap ( ) . unwrap ( ) ;
2021-12-09 11:13:12 +01:00
let expected = Filter ::from_str ( " channel = 'Mister Mv' " ) . unwrap ( ) . unwrap ( ) ;
2021-10-22 14:33:18 +02:00
assert_eq! ( condition , expected ) ;
// Array with Right and escaped simple quote
2021-11-06 01:32:12 +01:00
let condition =
Filter ::from_array ::< _ , Option < & str > > ( vec! [ Either ::Right ( " channel = 'Mister Mv' " ) ] )
. unwrap ( )
. unwrap ( ) ;
2021-12-09 11:13:12 +01:00
let expected = Filter ::from_str ( " channel = 'Mister Mv' " ) . unwrap ( ) . unwrap ( ) ;
2021-10-22 14:33:18 +02:00
assert_eq! ( condition , expected ) ;
// Simple with parenthesis
2021-11-06 01:32:12 +01:00
let condition =
Filter ::from_array ( vec! [ Either ::Left ( [ " (channel = mv) " ] ) ] ) . unwrap ( ) . unwrap ( ) ;
2021-12-09 11:13:12 +01:00
let expected = Filter ::from_str ( " (channel = mv) " ) . unwrap ( ) . unwrap ( ) ;
2021-10-22 14:33:18 +02:00
assert_eq! ( condition , expected ) ;
// Test that the facet condition is correctly generated.
2021-11-06 01:32:12 +01:00
let condition = Filter ::from_array ( vec! [
Either ::Right ( " channel = gotaga " ) ,
Either ::Left ( vec! [ " timestamp = 44 " , " channel != ponce " ] ) ,
] )
2021-10-22 14:33:18 +02:00
. unwrap ( )
. unwrap ( ) ;
2021-11-06 01:32:12 +01:00
let expected =
2021-12-09 11:13:12 +01:00
Filter ::from_str ( " channel = gotaga AND (timestamp = 44 OR channel != ponce) " )
. unwrap ( )
. unwrap ( ) ;
2021-10-22 14:33:18 +02:00
assert_eq! ( condition , expected ) ;
}
2021-11-07 01:52:19 +01:00
#[ test ]
fn not_filterable ( ) {
2022-08-04 10:46:10 +02:00
let index = TempIndex ::new ( ) ;
2021-11-07 01:52:19 +01:00
let rtxn = index . read_txn ( ) . unwrap ( ) ;
2021-12-09 11:13:12 +01:00
let filter = Filter ::from_str ( " _geoRadius(42, 150, 10) " ) . unwrap ( ) . unwrap ( ) ;
2021-11-07 01:52:19 +01:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . starts_with (
2022-03-15 22:12:51 +01:00
" Attribute `_geo` is not filterable. This index does not have configured filterable attributes. "
2021-11-07 01:52:19 +01:00
) ) ;
2023-02-06 16:50:27 +01:00
let filter = Filter ::from_str ( " _geoBoundingBox([42, 150], [30, 10]) " ) . unwrap ( ) . unwrap ( ) ;
2022-10-28 19:01:23 +02:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . starts_with (
" Attribute `_geo` is not filterable. This index does not have configured filterable attributes. "
) ) ;
2021-12-09 11:13:12 +01:00
let filter = Filter ::from_str ( " dog = \" bernese mountain \" " ) . unwrap ( ) . unwrap ( ) ;
2021-11-07 01:52:19 +01:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . starts_with (
2022-03-15 22:12:51 +01:00
" Attribute `dog` is not filterable. This index does not have configured filterable attributes. "
2021-11-07 01:52:19 +01:00
) ) ;
drop ( rtxn ) ;
2022-08-04 10:46:10 +02:00
index
. update_settings ( | settings | {
settings . set_searchable_fields ( vec! [ S ( " title " ) ] ) ;
settings . set_filterable_fields ( hashset! { S ( " title " ) } ) ;
} )
. unwrap ( ) ;
2021-11-07 01:52:19 +01:00
let rtxn = index . read_txn ( ) . unwrap ( ) ;
2021-12-09 11:13:12 +01:00
let filter = Filter ::from_str ( " _geoRadius(-100, 150, 10) " ) . unwrap ( ) . unwrap ( ) ;
2021-11-07 01:52:19 +01:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . starts_with (
" Attribute `_geo` is not filterable. Available filterable attributes are: `title`. "
) ) ;
2023-02-06 16:50:27 +01:00
let filter = Filter ::from_str ( " _geoBoundingBox([42, 150], [30, 10]) " ) . unwrap ( ) . unwrap ( ) ;
2022-10-28 19:01:23 +02:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . starts_with (
" Attribute `_geo` is not filterable. Available filterable attributes are: `title`. "
) ) ;
2021-12-09 11:13:12 +01:00
let filter = Filter ::from_str ( " name = 12 " ) . unwrap ( ) . unwrap ( ) ;
2021-11-07 01:52:19 +01:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . starts_with (
" Attribute `name` is not filterable. Available filterable attributes are: `title`. "
) ) ;
}
2022-06-09 16:03:49 +02:00
#[ test ]
fn escaped_quote_in_filter_value_2380 ( ) {
2022-08-04 10:46:10 +02:00
let index = TempIndex ::new ( ) ;
index
. add_documents ( documents! ( [
{
" id " : " test_1 " ,
" monitor_diagonal " : " 27' to 30' "
} ,
{
" id " : " test_2 " ,
" monitor_diagonal " : " 27 \" to 30 \" "
} ,
{
" id " : " test_3 " ,
" monitor_diagonal " : " 27 \" to 30' "
} ,
] ) )
. unwrap ( ) ;
2022-06-09 16:03:49 +02:00
2022-08-04 10:46:10 +02:00
index
. update_settings ( | settings | {
settings . set_filterable_fields ( hashset! ( S ( " monitor_diagonal " ) ) ) ;
} )
. unwrap ( ) ;
2022-06-09 16:03:49 +02:00
let rtxn = index . read_txn ( ) . unwrap ( ) ;
let mut search = crate ::Search ::new ( & rtxn , & index ) ;
// this filter is copy pasted from #2380 with the exact same espace sequence
2022-08-04 10:46:10 +02:00
search . filter ( Filter ::from_str ( " monitor_diagonal = '27 \" to 30 \\ '' " ) . unwrap ( ) . unwrap ( ) ) ;
2022-06-09 16:03:49 +02:00
let crate ::SearchResult { documents_ids , .. } = search . execute ( ) . unwrap ( ) ;
assert_eq! ( documents_ids , vec! [ 2 ] ) ;
2022-08-04 10:46:10 +02:00
search . filter ( Filter ::from_str ( r # "monitor_diagonal = "27' to 30'" "# ) . unwrap ( ) . unwrap ( ) ) ;
2022-06-09 16:03:49 +02:00
let crate ::SearchResult { documents_ids , .. } = search . execute ( ) . unwrap ( ) ;
assert_eq! ( documents_ids , vec! [ 0 ] ) ;
2022-08-04 10:46:10 +02:00
search . filter ( Filter ::from_str ( r # "monitor_diagonal = "27\" to 30\"" "# ) . unwrap ( ) . unwrap ( ) ) ;
2022-06-09 16:03:49 +02:00
let crate ::SearchResult { documents_ids , .. } = search . execute ( ) . unwrap ( ) ;
assert_eq! ( documents_ids , vec! [ 1 ] ) ;
2022-08-04 10:46:10 +02:00
search . filter ( Filter ::from_str ( r # "monitor_diagonal = "27\" to 30'" "# ) . unwrap ( ) . unwrap ( ) ) ;
2022-06-09 16:03:49 +02:00
let crate ::SearchResult { documents_ids , .. } = search . execute ( ) . unwrap ( ) ;
assert_eq! ( documents_ids , vec! [ 2 ] ) ;
}
2022-11-30 19:44:26 +01:00
#[ test ]
fn zero_radius ( ) {
let index = TempIndex ::new ( ) ;
index
. update_settings ( | settings | {
settings . set_filterable_fields ( hashset! { S ( " _geo " ) } ) ;
} )
. unwrap ( ) ;
index
. add_documents ( documents! ( [
{
" id " : 1 ,
" name " : " Nàpiz' Milano " ,
" address " : " Viale Vittorio Veneto, 30, 20124, Milan, Italy " ,
" type " : " pizza " ,
" rating " : 9 ,
" _geo " : {
" lat " : 45.4777599 ,
" lng " : 9.1967508
}
} ,
{
" id " : 2 ,
" name " : " Artico Gelateria Tradizionale " ,
" address " : " Via Dogana, 1, 20123 Milan, Italy " ,
" type " : " ice cream " ,
" rating " : 10 ,
" _geo " : {
" lat " : 45.4632046 ,
" lng " : 9.1719421
}
} ,
] ) )
. unwrap ( ) ;
let rtxn = index . read_txn ( ) . unwrap ( ) ;
let mut search = crate ::Search ::new ( & rtxn , & index ) ;
search . filter ( Filter ::from_str ( " _geoRadius(45.4777599, 9.1967508, 0) " ) . unwrap ( ) . unwrap ( ) ) ;
let crate ::SearchResult { documents_ids , .. } = search . execute ( ) . unwrap ( ) ;
assert_eq! ( documents_ids , vec! [ 0 ] ) ;
}
2021-10-22 14:33:18 +02:00
#[ test ]
fn geo_radius_error ( ) {
2022-08-04 10:46:10 +02:00
let index = TempIndex ::new ( ) ;
2021-10-22 14:33:18 +02:00
2022-08-04 10:46:10 +02:00
index
. update_settings ( | settings | {
settings . set_searchable_fields ( vec! [ S ( " _geo " ) , S ( " price " ) ] ) ; // to keep the fields order
settings . set_filterable_fields ( hashset! { S ( " _geo " ) , S ( " price " ) } ) ;
} )
. unwrap ( ) ;
2021-10-22 14:33:18 +02:00
let rtxn = index . read_txn ( ) . unwrap ( ) ;
2022-11-30 19:44:26 +01:00
2021-11-06 01:32:12 +01:00
// georadius have a bad latitude
2021-12-09 11:13:12 +01:00
let filter = Filter ::from_str ( " _geoRadius(-100, 150, 10) " ) . unwrap ( ) . unwrap ( ) ;
2021-11-06 01:32:12 +01:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
2021-10-22 14:33:18 +02:00
assert! (
2021-11-06 01:32:12 +01:00
error . to_string ( ) . starts_with (
" Bad latitude `-100`. Latitude must be contained between -90 and 90 degrees. "
) ,
2021-10-22 14:33:18 +02:00
" {} " ,
error . to_string ( )
) ;
// georadius have a bad latitude
2021-12-09 11:13:12 +01:00
let filter = Filter ::from_str ( " _geoRadius(-90.0000001, 150, 10) " ) . unwrap ( ) . unwrap ( ) ;
2021-11-06 01:32:12 +01:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains (
" Bad latitude `-90.0000001`. Latitude must be contained between -90 and 90 degrees. "
) ) ;
2021-10-22 14:33:18 +02:00
// georadius have a bad longitude
2021-12-09 11:13:12 +01:00
let filter = Filter ::from_str ( " _geoRadius(-10, 250, 10) " ) . unwrap ( ) . unwrap ( ) ;
2021-11-06 01:32:12 +01:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! (
error . to_string ( ) . contains (
" Bad longitude `250`. Longitude must be contained between -180 and 180 degrees. "
) ,
" {} " ,
error . to_string ( ) ,
) ;
2021-10-22 14:33:18 +02:00
// georadius have a bad longitude
2021-12-09 11:13:12 +01:00
let filter = Filter ::from_str ( " _geoRadius(-10, 180.000001, 10) " ) . unwrap ( ) . unwrap ( ) ;
2021-11-06 01:32:12 +01:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains (
" Bad longitude `180.000001`. Longitude must be contained between -180 and 180 degrees. "
) ) ;
2021-10-22 14:33:18 +02:00
}
2021-12-07 17:20:11 +01:00
2022-10-28 19:01:23 +02:00
#[ test ]
fn geo_bounding_box_error ( ) {
let index = TempIndex ::new ( ) ;
index
. update_settings ( | settings | {
settings . set_searchable_fields ( vec! [ S ( " _geo " ) , S ( " price " ) ] ) ; // to keep the fields order
settings . set_filterable_fields ( hashset! { S ( " _geo " ) , S ( " price " ) } ) ;
} )
. unwrap ( ) ;
let rtxn = index . read_txn ( ) . unwrap ( ) ;
// geoboundingbox top left coord have a bad latitude
let filter =
2023-02-06 16:50:27 +01:00
Filter ::from_str ( " _geoBoundingBox([-90.0000001, 150], [30, 10]) " ) . unwrap ( ) . unwrap ( ) ;
2022-10-28 19:01:23 +02:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! (
error . to_string ( ) . starts_with (
" Bad latitude `-90.0000001`. Latitude must be contained between -90 and 90 degrees. "
) ,
" {} " ,
error . to_string ( )
) ;
// geoboundingbox top left coord have a bad latitude
let filter =
2023-02-06 16:50:27 +01:00
Filter ::from_str ( " _geoBoundingBox([90.0000001, 150], [30, 10]) " ) . unwrap ( ) . unwrap ( ) ;
2022-10-28 19:01:23 +02:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! (
error . to_string ( ) . starts_with (
" Bad latitude `90.0000001`. Latitude must be contained between -90 and 90 degrees. "
) ,
" {} " ,
error . to_string ( )
) ;
// geoboundingbox bottom right coord have a bad latitude
let filter =
2023-02-06 16:50:27 +01:00
Filter ::from_str ( " _geoBoundingBox([30, 10], [-90.0000001, 150]) " ) . unwrap ( ) . unwrap ( ) ;
2022-10-28 19:01:23 +02:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains (
" Bad latitude `-90.0000001`. Latitude must be contained between -90 and 90 degrees. "
) ) ;
// geoboundingbox bottom right coord have a bad latitude
let filter =
2023-02-06 16:50:27 +01:00
Filter ::from_str ( " _geoBoundingBox([30, 10], [90.0000001, 150]) " ) . unwrap ( ) . unwrap ( ) ;
2022-10-28 19:01:23 +02:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains (
" Bad latitude `90.0000001`. Latitude must be contained between -90 and 90 degrees. "
) ) ;
// geoboundingbox top left coord have a bad longitude
let filter =
2023-02-06 16:50:27 +01:00
Filter ::from_str ( " _geoBoundingBox([-10, 180.000001], [30, 10]) " ) . unwrap ( ) . unwrap ( ) ;
2022-10-28 19:01:23 +02:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains (
" Bad longitude `180.000001`. Longitude must be contained between -180 and 180 degrees. "
) ) ;
// geoboundingbox top left coord have a bad longitude
let filter =
2023-02-06 16:50:27 +01:00
Filter ::from_str ( " _geoBoundingBox([-10, -180.000001], [30, 10]) " ) . unwrap ( ) . unwrap ( ) ;
2022-10-28 19:01:23 +02:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains (
" Bad longitude `-180.000001`. Longitude must be contained between -180 and 180 degrees. "
) ) ;
// geoboundingbox bottom right coord have a bad longitude
let filter =
2023-02-06 16:50:27 +01:00
Filter ::from_str ( " _geoBoundingBox([30, 10], [-10, -180.000001]) " ) . unwrap ( ) . unwrap ( ) ;
2022-10-28 19:01:23 +02:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains (
" Bad longitude `-180.000001`. Longitude must be contained between -180 and 180 degrees. "
) ) ;
// geoboundingbox bottom right coord have a bad longitude
let filter =
2023-02-06 16:50:27 +01:00
Filter ::from_str ( " _geoBoundingBox([30, 10], [-10, 180.000001]) " ) . unwrap ( ) . unwrap ( ) ;
2022-10-28 19:01:23 +02:00
let error = filter . evaluate ( & rtxn , & index ) . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains (
" Bad longitude `180.000001`. Longitude must be contained between -180 and 180 degrees. "
) ) ;
}
2021-12-07 17:20:11 +01:00
#[ test ]
fn filter_depth ( ) {
// generates a big (2 MiB) filter with too much of ORs.
let tipic_filter = " account_ids=14361 OR " ;
let mut filter_string = String ::with_capacity ( tipic_filter . len ( ) * 14360 ) ;
for i in 1 ..= 14361 {
let _ = write! ( & mut filter_string , " account_ids={} " , i ) ;
if i ! = 14361 {
let _ = write! ( & mut filter_string , " OR " ) ;
}
}
2022-06-20 18:46:57 +02:00
// Note: the filter used to be rejected for being too deep, but that is
// no longer the case
let filter = Filter ::from_str ( & filter_string ) . unwrap ( ) ;
assert! ( filter . is_some ( ) ) ;
2021-12-07 17:20:11 +01:00
}
2021-12-09 11:14:51 +01:00
#[ test ]
fn empty_filter ( ) {
let option = Filter ::from_str ( " " ) . unwrap ( ) ;
assert_eq! ( option , None ) ;
}
2022-11-08 10:21:54 +01:00
#[ test ]
fn non_finite_float ( ) {
let index = TempIndex ::new ( ) ;
index
. update_settings ( | settings | {
settings . set_searchable_fields ( vec! [ S ( " price " ) ] ) ; // to keep the fields order
settings . set_filterable_fields ( hashset! { S ( " price " ) } ) ;
} )
. unwrap ( ) ;
index
. add_documents ( documents! ( [
{
" id " : " test_1 " ,
" price " : " inf "
} ,
{
" id " : " test_2 " ,
" price " : " 2000 "
} ,
{
" id " : " test_3 " ,
" price " : " infinity "
} ,
] ) )
. unwrap ( ) ;
let rtxn = index . read_txn ( ) . unwrap ( ) ;
let filter = Filter ::from_str ( " price = inf " ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert! ( result . contains ( 0 ) ) ;
let filter = Filter ::from_str ( " price < inf " ) . unwrap ( ) . unwrap ( ) ;
assert! ( matches! (
filter . evaluate ( & rtxn , & index ) ,
Err ( crate ::Error ::UserError ( crate ::error ::UserError ::InvalidFilter ( _ ) ) )
) ) ;
let filter = Filter ::from_str ( " price = NaN " ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert! ( result . is_empty ( ) ) ;
let filter = Filter ::from_str ( " price < NaN " ) . unwrap ( ) . unwrap ( ) ;
assert! ( matches! (
filter . evaluate ( & rtxn , & index ) ,
Err ( crate ::Error ::UserError ( crate ::error ::UserError ::InvalidFilter ( _ ) ) )
) ) ;
let filter = Filter ::from_str ( " price = infinity " ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert! ( result . contains ( 2 ) ) ;
let filter = Filter ::from_str ( " price < infinity " ) . unwrap ( ) . unwrap ( ) ;
assert! ( matches! (
filter . evaluate ( & rtxn , & index ) ,
Err ( crate ::Error ::UserError ( crate ::error ::UserError ::InvalidFilter ( _ ) ) )
) ) ;
}
2022-12-07 14:11:20 +01:00
#[ test ]
fn filter_number ( ) {
let index = TempIndex ::new ( ) ;
index
. update_settings ( | settings | {
settings . set_primary_key ( " id " . to_owned ( ) ) ;
settings . set_filterable_fields ( hashset! { S ( " id " ) , S ( " one " ) , S ( " two " ) } ) ;
} )
. unwrap ( ) ;
let mut docs = vec! [ ] ;
for i in 0 .. 100 {
docs . push ( serde_json ::json! ( { " id " : i , " two " : i % 10 } ) ) ;
}
index . add_documents ( documents! ( docs ) ) . unwrap ( ) ;
let rtxn = index . read_txn ( ) . unwrap ( ) ;
for i in 0 .. 100 {
let filter_str = format! ( " id = {i} " ) ;
let filter = Filter ::from_str ( & filter_str ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert_eq! ( result , RoaringBitmap ::from_iter ( [ i ] ) ) ;
}
for i in 0 .. 100 {
let filter_str = format! ( " id > {i} " ) ;
let filter = Filter ::from_str ( & filter_str ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert_eq! ( result , RoaringBitmap ::from_iter ( ( i + 1 ) .. 100 ) ) ;
}
for i in 0 .. 100 {
let filter_str = format! ( " id < {i} " ) ;
let filter = Filter ::from_str ( & filter_str ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert_eq! ( result , RoaringBitmap ::from_iter ( 0 .. i ) ) ;
}
for i in 0 .. 100 {
let filter_str = format! ( " id <= {i} " ) ;
let filter = Filter ::from_str ( & filter_str ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert_eq! ( result , RoaringBitmap ::from_iter ( 0 ..= i ) ) ;
}
for i in 0 .. 100 {
let filter_str = format! ( " id >= {i} " ) ;
let filter = Filter ::from_str ( & filter_str ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert_eq! ( result , RoaringBitmap ::from_iter ( i .. 100 ) ) ;
}
for i in 0 .. 100 {
for j in i .. 100 {
let filter_str = format! ( " id {i} TO {j} " ) ;
let filter = Filter ::from_str ( & filter_str ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert_eq! ( result , RoaringBitmap ::from_iter ( i ..= j ) ) ;
}
}
let filter = Filter ::from_str ( " one >= 0 OR one <= 0 " ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert_eq! ( result , RoaringBitmap ::default ( ) ) ;
let filter = Filter ::from_str ( " one = 0 " ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert_eq! ( result , RoaringBitmap ::default ( ) ) ;
for i in 0 .. 10 {
for j in i .. 10 {
let filter_str = format! ( " two {i} TO {j} " ) ;
let filter = Filter ::from_str ( & filter_str ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert_eq! (
result ,
RoaringBitmap ::from_iter ( ( 0 .. 100 ) . filter ( | x | ( i ..= j ) . contains ( & ( x % 10 ) ) ) )
) ;
}
}
let filter = Filter ::from_str ( " two != 0 " ) . unwrap ( ) . unwrap ( ) ;
let result = filter . evaluate ( & rtxn , & index ) . unwrap ( ) ;
assert_eq! ( result , RoaringBitmap ::from_iter ( ( 0 .. 100 ) . filter ( | x | x % 10 ! = 0 ) ) ) ;
}
2021-10-22 14:33:18 +02:00
}