2021-05-03 12:51:33 +02:00
use std ::collections ::HashSet ;
2020-11-26 20:42:54 +01:00
use std ::fmt ::Debug ;
2021-06-16 18:33:33 +02:00
use std ::ops ::Bound ::{ self , Excluded , Included } ;
2021-06-14 16:46:19 +02:00
use std ::result ::Result as StdResult ;
2020-11-26 20:42:54 +01:00
use std ::str ::FromStr ;
2021-09-15 12:57:18 +02:00
use crate ::error ::UserError as IError ;
2021-01-07 10:17:27 +01:00
use either ::Either ;
2021-04-07 11:57:16 +02:00
use heed ::types ::DecodeIgnore ;
2021-07-27 16:24:21 +02:00
use itertools ::Itertools ;
2020-11-26 20:42:54 +01:00
use log ::debug ;
use pest ::error ::{ Error as PestError , ErrorVariant } ;
use pest ::iterators ::{ Pair , Pairs } ;
use pest ::Parser ;
use roaring ::RoaringBitmap ;
2021-09-15 12:57:18 +02:00
use nom ::{
branch ::alt ,
bytes ::complete ::{ tag , take_while1 } ,
character ::complete ::{ char , multispace0 } ,
combinator ::map ,
error ::ParseError ,
multi ::many0 ,
sequence ::{ delimited , preceded , tuple } ,
IResult ,
} ;
2021-06-01 15:25:17 +02:00
use self ::FilterCondition ::* ;
2021-05-03 11:45:45 +02:00
use self ::Operator ::* ;
2021-06-16 18:33:33 +02:00
use super ::parser ::{ FilterParser , Rule , PREC_CLIMBER } ;
2021-06-23 10:29:00 +02:00
use super ::FacetNumberRange ;
2021-06-16 18:33:33 +02:00
use crate ::error ::UserError ;
2021-07-17 12:50:01 +02:00
use crate ::heed_codec ::facet ::{
FacetLevelValueF64Codec , FacetStringLevelZeroCodec , FacetStringLevelZeroValueCodec ,
} ;
2021-09-09 12:20:08 +02:00
use crate ::{
distance_between_two_points , CboRoaringBitmapCodec , FieldId , FieldsIdsMap , Index , Result ,
} ;
2020-11-26 20:42:54 +01:00
2021-05-03 11:45:45 +02:00
#[ derive(Debug, Clone, PartialEq) ]
pub enum Operator {
2021-04-07 11:57:16 +02:00
GreaterThan ( f64 ) ,
GreaterThanOrEqual ( f64 ) ,
2021-05-03 11:45:45 +02:00
Equal ( Option < f64 > , String ) ,
NotEqual ( Option < f64 > , String ) ,
2021-04-07 11:57:16 +02:00
LowerThan ( f64 ) ,
LowerThanOrEqual ( f64 ) ,
Between ( f64 , f64 ) ,
2021-08-26 16:38:29 +02:00
GeoLowerThan ( [ f64 ; 2 ] , f64 ) ,
GeoGreaterThan ( [ f64 ; 2 ] , f64 ) ,
2020-11-26 20:42:54 +01:00
}
2021-05-03 11:45:45 +02:00
impl Operator {
2020-11-26 20:42:54 +01:00
/// This method can return two operations in case it must express
/// an OR operation for the between case (i.e. `TO`).
fn negate ( self ) -> ( Self , Option < Self > ) {
match self {
2021-06-16 18:33:33 +02:00
GreaterThan ( n ) = > ( LowerThanOrEqual ( n ) , None ) ,
2021-05-03 11:45:45 +02:00
GreaterThanOrEqual ( n ) = > ( LowerThan ( n ) , None ) ,
2021-06-16 18:33:33 +02:00
Equal ( n , s ) = > ( NotEqual ( n , s ) , None ) ,
NotEqual ( n , s ) = > ( Equal ( n , s ) , None ) ,
LowerThan ( n ) = > ( GreaterThanOrEqual ( n ) , None ) ,
LowerThanOrEqual ( n ) = > ( GreaterThan ( n ) , None ) ,
Between ( n , m ) = > ( LowerThan ( n ) , Some ( GreaterThan ( m ) ) ) ,
2021-08-26 16:38:29 +02:00
GeoLowerThan ( point , distance ) = > ( GeoGreaterThan ( point , distance ) , None ) ,
GeoGreaterThan ( point , distance ) = > ( GeoLowerThan ( point , distance ) , None ) ,
2020-11-26 20:42:54 +01:00
}
}
}
#[ derive(Debug, Clone, PartialEq) ]
2021-06-01 15:25:17 +02:00
pub enum FilterCondition {
2021-05-03 11:45:45 +02:00
Operator ( FieldId , Operator ) ,
2020-11-26 20:42:54 +01:00
Or ( Box < Self > , Box < Self > ) ,
And ( Box < Self > , Box < Self > ) ,
2021-07-27 16:24:21 +02:00
Empty ,
2020-11-26 20:42:54 +01:00
}
2021-09-15 12:57:18 +02:00
struct ParseContext < ' a > {
fields_ids_map : & ' a FieldsIdsMap ,
filterable_fields : & ' a HashSet < String > ,
}
// impl From<std::>
impl < ' a > ParseContext < ' a > {
fn parse_or_nom ( & ' a self , input : & ' a str ) -> IResult < & ' a str , FilterCondition > {
let ( input , lhs ) = self . parse_and_nom ( input ) ? ;
let ( input , ors ) = many0 ( preceded ( tag ( " OR " ) , | c | Self ::parse_or_nom ( self , c ) ) ) ( input ) ? ;
let expr = ors
. into_iter ( )
. fold ( lhs , | acc , branch | FilterCondition ::Or ( Box ::new ( acc ) , Box ::new ( branch ) ) ) ;
Ok ( ( input , expr ) )
}
fn parse_and_nom ( & ' a self , input : & ' a str ) -> IResult < & ' a str , FilterCondition > {
let ( input , lhs ) = self . parse_not_nom ( input ) ? ;
let ( input , ors ) = many0 ( preceded ( tag ( " AND " ) , | c | Self ::parse_and_nom ( self , c ) ) ) ( input ) ? ;
let expr = ors
. into_iter ( )
. fold ( lhs , | acc , branch | FilterCondition ::And ( Box ::new ( acc ) , Box ::new ( branch ) ) ) ;
Ok ( ( input , expr ) )
}
fn parse_not_nom ( & ' a self , input : & ' a str ) -> IResult < & ' a str , FilterCondition > {
let r = alt ( (
map (
preceded ( alt ( ( Self ::ws ( tag ( " ! " ) ) , Self ::ws ( tag ( " NOT " ) ) ) ) , | c | {
Self ::parse_condition_expression ( self , c )
} ) ,
| e | e . negate ( ) ,
) ,
| c | Self ::parse_condition_expression ( self , c ) ,
) ) ( input ) ;
return r ;
}
fn ws < ' b , F : ' b , O , E : ParseError < & ' b str > > (
inner : F ,
) -> impl FnMut ( & ' b str ) -> IResult < & ' b str , O , E >
where
F : Fn ( & ' b str ) -> IResult < & ' b str , O , E > ,
{
delimited ( multispace0 , inner , multispace0 )
}
fn parse_simple_condition (
& self ,
input : & ' a str ,
) -> StdResult < ( & ' a str , FilterCondition ) , UserError > {
let operator = alt ( ( tag ( " > " ) , tag ( " >= " ) , tag ( " = " ) , tag ( " < " ) , tag ( " != " ) , tag ( " <= " ) ) ) ;
let ( input , ( key , op , value ) ) =
match tuple ( ( Self ::ws ( Self ::parse_key ) , operator , Self ::ws ( Self ::parse_key ) ) ) ( input ) {
Ok ( ( input , ( key , op , value ) ) ) = > ( input , ( key , op , value ) ) ,
Err ( _ ) = > return Err ( UserError ::InvalidFilterAttributeNom ) ,
} ;
let fid = match field_id_by_key ( self . fields_ids_map , self . filterable_fields , key ) ? {
Some ( fid ) = > fid ,
None = > return Err ( UserError ::InvalidFilterAttributeNom ) ,
} ;
let r = nom_parse ::< f64 > ( value ) ;
let k = match op {
" > " = > Operator ( fid , GreaterThan ( value . parse ::< f64 > ( ) ? ) ) ,
" < " = > Operator ( fid , LowerThan ( value . parse ::< f64 > ( ) ? ) ) ,
" <= " = > Operator ( fid , LowerThanOrEqual ( value . parse ::< f64 > ( ) ? ) ) ,
" >= " = > Operator ( fid , GreaterThanOrEqual ( value . parse ::< f64 > ( ) ? ) ) ,
" = " = > Operator ( fid , Equal ( r . 0. ok ( ) , value . to_string ( ) . to_lowercase ( ) ) ) ,
" != " = > Operator ( fid , NotEqual ( r . 0. ok ( ) , value . to_string ( ) . to_lowercase ( ) ) ) ,
_ = > unreachable! ( ) ,
} ;
Ok ( ( input , k ) )
}
fn parse_range_condition (
& ' a self ,
input : & ' a str ,
) -> StdResult < ( & str , FilterCondition ) , UserError > {
let ( input , ( key , from , _ , to ) ) = match tuple ( (
Self ::ws ( Self ::parse_key ) ,
Self ::ws ( Self ::parse_key ) ,
tag ( " TO " ) ,
Self ::ws ( Self ::parse_key ) ,
) ) ( input )
{
Ok ( ( input , ( key , from , tag , to ) ) ) = > ( input , ( key , from , tag , to ) ) ,
Err ( _ ) = > return Err ( UserError ::InvalidFilterAttributeNom ) ,
} ;
let fid = match field_id_by_key ( self . fields_ids_map , self . filterable_fields , key ) ? {
Some ( fid ) = > fid ,
None = > return Err ( UserError ::InvalidFilterAttributeNom ) ,
} ;
let res = Operator ( fid , Between ( from . parse ::< f64 > ( ) ? , to . parse ::< f64 > ( ) ? ) ) ;
Ok ( ( input , res ) )
}
fn parse_condition ( & ' a self , input : & ' a str ) -> IResult < & ' a str , FilterCondition > {
let l1 = | c | self . wrap ( | c | self . parse_simple_condition ( c ) , c ) ;
let l2 = | c | self . wrap ( | c | self . parse_range_condition ( c ) , c ) ;
let ( input , condition ) = match alt ( ( l1 , l2 ) ) ( input ) {
Ok ( ( i , c ) ) = > ( i , c ) ,
Err ( _ ) = > {
return Err ( nom ::Err ::Error ( nom ::error ::Error ::from_error_kind (
" foo " ,
nom ::error ::ErrorKind ::Fail ,
) ) )
}
} ;
Ok ( ( input , condition ) )
}
fn wrap < F , E > ( & ' a self , inner : F , input : & ' a str ) -> IResult < & ' a str , FilterCondition >
where
F : Fn ( & ' a str ) -> StdResult < ( & ' a str , FilterCondition ) , E > ,
{
match inner ( input ) {
Ok ( e ) = > Ok ( e ) ,
Err ( _ ) = > {
return Err ( nom ::Err ::Error ( nom ::error ::Error ::from_error_kind (
" foo " ,
nom ::error ::ErrorKind ::Fail ,
) ) )
}
}
}
fn parse_condition_expression ( & ' a self , input : & ' a str ) -> IResult < & str , FilterCondition > {
return alt ( (
delimited (
Self ::ws ( char ( '(' ) ) ,
| c | Self ::parse_expression ( self , c ) ,
Self ::ws ( char ( ')' ) ) ,
) ,
| c | Self ::parse_condition ( self , c ) ,
) ) ( input ) ;
}
fn parse_key ( input : & str ) -> IResult < & str , & str > {
let key = | input | take_while1 ( Self ::is_key_component ) ( input ) ;
alt ( ( key , delimited ( char ( '"' ) , key , char ( '"' ) ) ) ) ( input )
}
fn is_key_component ( c : char ) -> bool {
c . is_alphanumeric ( ) | | [ '_' , '-' , '.' ] . contains ( & c )
}
pub fn parse_expression ( & ' a self , input : & ' a str ) -> IResult < & ' a str , FilterCondition > {
self . parse_or_nom ( input )
}
}
//for nom
2021-06-01 15:25:17 +02:00
impl FilterCondition {
2021-01-07 10:17:27 +01:00
pub fn from_array < I , J , A , B > (
rtxn : & heed ::RoTxn ,
index : & Index ,
array : I ,
2021-06-14 16:46:19 +02:00
) -> Result < Option < FilterCondition > >
2021-06-16 18:33:33 +02:00
where
I : IntoIterator < Item = Either < J , B > > ,
J : IntoIterator < Item = A > ,
A : AsRef < str > ,
B : AsRef < str > ,
2021-01-07 10:17:27 +01:00
{
let mut ands = None ;
for either in array {
match either {
Either ::Left ( array ) = > {
let mut ors = None ;
for rule in array {
2021-06-01 15:25:17 +02:00
let condition = FilterCondition ::from_str ( rtxn , index , rule . as_ref ( ) ) ? ;
2021-01-07 10:17:27 +01:00
ors = match ors . take ( ) {
Some ( ors ) = > Some ( Or ( Box ::new ( ors ) , Box ::new ( condition ) ) ) ,
None = > Some ( condition ) ,
} ;
}
if let Some ( rule ) = ors {
ands = match ands . take ( ) {
Some ( ands ) = > Some ( And ( Box ::new ( ands ) , Box ::new ( rule ) ) ) ,
None = > Some ( rule ) ,
} ;
}
2021-06-16 18:33:33 +02:00
}
2021-01-07 10:17:27 +01:00
Either ::Right ( rule ) = > {
2021-06-01 15:25:17 +02:00
let condition = FilterCondition ::from_str ( rtxn , index , rule . as_ref ( ) ) ? ;
2021-01-07 10:17:27 +01:00
ands = match ands . take ( ) {
Some ( ands ) = > Some ( And ( Box ::new ( ands ) , Box ::new ( condition ) ) ) ,
None = > Some ( condition ) ,
} ;
}
}
}
Ok ( ands )
}
2020-11-26 20:42:54 +01:00
pub fn from_str (
rtxn : & heed ::RoTxn ,
index : & Index ,
expression : & str ,
2021-09-15 12:57:18 +02:00
) -> Result < FilterCondition > {
let fields_ids_map = index . fields_ids_map ( rtxn ) ? ;
let filterable_fields = index . filterable_fields ( rtxn ) ? ;
let ctx =
ParseContext { fields_ids_map : & fields_ids_map , filterable_fields : & filterable_fields } ;
match ctx . parse_expression ( expression ) {
Ok ( ( _ , fc ) ) = > Ok ( fc ) ,
Err ( e ) = > {
println! ( " {:?} " , e ) ;
unreachable! ( )
}
}
}
}
impl FilterCondition {
pub fn from_array_pest < I , J , A , B > (
rtxn : & heed ::RoTxn ,
index : & Index ,
array : I ,
) -> Result < Option < FilterCondition > >
where
I : IntoIterator < Item = Either < J , B > > ,
J : IntoIterator < Item = A > ,
A : AsRef < str > ,
B : AsRef < str > ,
{
let mut ands = None ;
for either in array {
match either {
Either ::Left ( array ) = > {
let mut ors = None ;
for rule in array {
let condition = FilterCondition ::from_str ( rtxn , index , rule . as_ref ( ) ) ? ;
ors = match ors . take ( ) {
Some ( ors ) = > Some ( Or ( Box ::new ( ors ) , Box ::new ( condition ) ) ) ,
None = > Some ( condition ) ,
} ;
}
if let Some ( rule ) = ors {
ands = match ands . take ( ) {
Some ( ands ) = > Some ( And ( Box ::new ( ands ) , Box ::new ( rule ) ) ) ,
None = > Some ( rule ) ,
} ;
}
}
Either ::Right ( rule ) = > {
let condition = FilterCondition ::from_str ( rtxn , index , rule . as_ref ( ) ) ? ;
ands = match ands . take ( ) {
Some ( ands ) = > Some ( And ( Box ::new ( ands ) , Box ::new ( condition ) ) ) ,
None = > Some ( condition ) ,
} ;
}
}
}
Ok ( ands )
}
pub fn from_str_pest (
rtxn : & heed ::RoTxn ,
index : & Index ,
expression : & str ,
2021-06-16 18:33:33 +02:00
) -> Result < FilterCondition > {
2020-11-26 20:42:54 +01:00
let fields_ids_map = index . fields_ids_map ( rtxn ) ? ;
2021-07-27 16:24:21 +02:00
let filterable_fields = index . filterable_fields ( rtxn ) ? ;
2021-06-16 18:33:33 +02:00
let lexed =
FilterParser ::parse ( Rule ::prgm , expression ) . map_err ( UserError ::InvalidFilter ) ? ;
2021-06-01 15:25:17 +02:00
FilterCondition ::from_pairs ( & fields_ids_map , & filterable_fields , lexed )
2020-11-26 20:42:54 +01:00
}
fn from_pairs (
fim : & FieldsIdsMap ,
2021-07-27 16:24:21 +02:00
ff : & HashSet < String > ,
2020-11-26 20:42:54 +01:00
expression : Pairs < Rule > ,
2021-06-16 18:33:33 +02:00
) -> Result < Self > {
2020-11-26 20:42:54 +01:00
PREC_CLIMBER . climb (
expression ,
| pair : Pair < Rule > | match pair . as_rule ( ) {
Rule ::greater = > Ok ( Self ::greater_than ( fim , ff , pair ) ? ) ,
Rule ::geq = > Ok ( Self ::greater_than_or_equal ( fim , ff , pair ) ? ) ,
Rule ::eq = > Ok ( Self ::equal ( fim , ff , pair ) ? ) ,
Rule ::neq = > Ok ( Self ::equal ( fim , ff , pair ) ? . negate ( ) ) ,
Rule ::leq = > Ok ( Self ::lower_than_or_equal ( fim , ff , pair ) ? ) ,
Rule ::less = > Ok ( Self ::lower_than ( fim , ff , pair ) ? ) ,
Rule ::between = > Ok ( Self ::between ( fim , ff , pair ) ? ) ,
2021-09-02 15:55:19 +02:00
Rule ::geo_radius = > Ok ( Self ::geo_radius ( fim , ff , pair ) ? ) ,
2020-11-26 20:42:54 +01:00
Rule ::not = > Ok ( Self ::from_pairs ( fim , ff , pair . into_inner ( ) ) ? . negate ( ) ) ,
Rule ::prgm = > Self ::from_pairs ( fim , ff , pair . into_inner ( ) ) ,
Rule ::term = > Self ::from_pairs ( fim , ff , pair . into_inner ( ) ) ,
_ = > unreachable! ( ) ,
} ,
2021-06-16 18:33:33 +02:00
| lhs : Result < Self > , op : Pair < Rule > , rhs : Result < Self > | match op . as_rule ( ) {
Rule ::or = > Ok ( Or ( Box ::new ( lhs ? ) , Box ::new ( rhs ? ) ) ) ,
Rule ::and = > Ok ( And ( Box ::new ( lhs ? ) , Box ::new ( rhs ? ) ) ) ,
_ = > unreachable! ( ) ,
2020-11-26 20:42:54 +01:00
} ,
)
}
2021-06-01 15:25:17 +02:00
fn negate ( self ) -> FilterCondition {
2020-11-26 20:42:54 +01:00
match self {
2021-05-03 11:45:45 +02:00
Operator ( fid , op ) = > match op . negate ( ) {
( op , None ) = > Operator ( fid , op ) ,
( a , Some ( b ) ) = > Or ( Box ::new ( Operator ( fid , a ) ) , Box ::new ( Operator ( fid , b ) ) ) ,
2021-04-07 11:57:16 +02:00
} ,
2020-11-26 20:42:54 +01:00
Or ( a , b ) = > And ( Box ::new ( a . negate ( ) ) , Box ::new ( b . negate ( ) ) ) ,
And ( a , b ) = > Or ( Box ::new ( a . negate ( ) ) , Box ::new ( b . negate ( ) ) ) ,
2021-07-27 16:24:21 +02:00
Empty = > Empty ,
2020-11-26 20:42:54 +01:00
}
}
2021-09-02 15:55:19 +02:00
fn geo_radius (
fields_ids_map : & FieldsIdsMap ,
filterable_fields : & HashSet < String > ,
item : Pair < Rule > ,
) -> Result < FilterCondition > {
if ! filterable_fields . contains ( " _geo " ) {
return Err ( UserError ::InvalidFilterAttribute ( PestError ::new_from_span (
ErrorVariant ::CustomError {
message : format ! (
" attribute `_geo` is not filterable, available filterable attributes are: {} " ,
filterable_fields . iter ( ) . join ( " , " ) ,
) ,
} ,
item . as_span ( ) ,
) ) ) ? ;
}
2021-08-26 16:38:29 +02:00
let mut items = item . into_inner ( ) ;
let fid = match fields_ids_map . id ( " _geo " ) {
Some ( fid ) = > fid ,
None = > return Ok ( Empty ) ,
} ;
2021-09-06 17:07:34 +02:00
let parameters_item = items . next ( ) . unwrap ( ) ;
// We don't need more than 3 parameters, but to handle errors correctly we are still going
// to extract the first 4 parameters
let param_span = parameters_item . as_span ( ) ;
let parameters = parameters_item
. into_inner ( )
. take ( 4 )
. map ( | param | ( param . clone ( ) , param . as_span ( ) ) )
. map ( | ( param , span ) | pest_parse ( param ) . 0. map ( | arg | ( arg , span ) ) )
. collect ::< StdResult < Vec < ( f64 , _ ) > , _ > > ( )
. map_err ( UserError ::InvalidFilter ) ? ;
if parameters . len ( ) ! = 3 {
return Err ( UserError ::InvalidFilter ( PestError ::new_from_span (
ErrorVariant ::CustomError {
message : format ! ( " The `_geoRadius` filter expect three arguments: `_geoRadius(latitude, longitude, radius)` " ) ,
} ,
2021-09-09 12:20:08 +02:00
// we want to point to the last parameters and if there was no parameters we
2021-09-06 17:07:34 +02:00
// point to the parenthesis
2021-09-09 12:20:08 +02:00
parameters . last ( ) . map ( | param | param . 1. clone ( ) ) . unwrap_or ( param_span ) ,
) ) ) ? ;
2021-09-06 17:07:34 +02:00
}
let ( lat , lng , distance ) = ( & parameters [ 0 ] , & parameters [ 1 ] , parameters [ 2 ] . 0 ) ;
2021-10-07 15:42:08 +02:00
if ! ( - 90. 0 ..= 90.0 ) . contains ( & lat . 0 ) {
2021-09-06 17:07:34 +02:00
return Err ( UserError ::InvalidFilter ( PestError ::new_from_span (
ErrorVariant ::CustomError {
2021-10-07 15:42:08 +02:00
message : format ! ( " Latitude must be contained between -90 and 90 degrees. " ) ,
} ,
lat . 1. clone ( ) ,
) ) ) ? ;
} else if ! ( - 180. 0 ..= 180.0 ) . contains ( & lng . 0 ) {
return Err ( UserError ::InvalidFilter ( PestError ::new_from_span (
ErrorVariant ::CustomError {
message : format ! ( " Longitude must be contained between -180 and 180 degrees. " ) ,
2021-09-06 17:07:34 +02:00
} ,
2021-10-07 15:42:08 +02:00
lng . 1. clone ( ) ,
2021-09-06 17:07:34 +02:00
) ) ) ? ;
}
Ok ( Operator ( fid , GeoLowerThan ( [ lat . 0 , lng . 0 ] , distance ) ) )
2021-08-26 16:38:29 +02:00
}
2020-11-26 20:42:54 +01:00
fn between (
fields_ids_map : & FieldsIdsMap ,
2021-07-27 16:24:21 +02:00
filterable_fields : & HashSet < String > ,
2020-11-26 20:42:54 +01:00
item : Pair < Rule > ,
2021-06-16 18:33:33 +02:00
) -> Result < FilterCondition > {
2020-11-26 20:42:54 +01:00
let mut items = item . into_inner ( ) ;
2021-07-27 16:24:21 +02:00
let fid = match field_id ( fields_ids_map , filterable_fields , & mut items )
. map_err ( UserError ::InvalidFilterAttribute ) ?
{
Some ( fid ) = > fid ,
None = > return Ok ( Empty ) ,
} ;
2021-05-03 11:45:45 +02:00
let ( lresult , _ ) = pest_parse ( items . next ( ) . unwrap ( ) ) ;
let ( rresult , _ ) = pest_parse ( items . next ( ) . unwrap ( ) ) ;
2021-06-15 17:22:04 +02:00
let lvalue = lresult . map_err ( UserError ::InvalidFilter ) ? ;
let rvalue = rresult . map_err ( UserError ::InvalidFilter ) ? ;
2021-05-03 11:45:45 +02:00
Ok ( Operator ( fid , Between ( lvalue , rvalue ) ) )
2020-11-26 20:42:54 +01:00
}
fn equal (
fields_ids_map : & FieldsIdsMap ,
2021-07-27 16:24:21 +02:00
filterable_fields : & HashSet < String > ,
2020-11-26 20:42:54 +01:00
item : Pair < Rule > ,
2021-06-16 18:33:33 +02:00
) -> Result < FilterCondition > {
2020-11-26 20:42:54 +01:00
let mut items = item . into_inner ( ) ;
2021-07-27 16:24:21 +02:00
let fid = match field_id ( fields_ids_map , filterable_fields , & mut items )
. map_err ( UserError ::InvalidFilterAttribute ) ?
{
Some ( fid ) = > fid ,
None = > return Ok ( Empty ) ,
} ;
2021-05-03 11:45:45 +02:00
2020-11-26 20:42:54 +01:00
let value = items . next ( ) . unwrap ( ) ;
2021-05-03 11:45:45 +02:00
let ( result , svalue ) = pest_parse ( value ) ;
2021-05-03 15:58:47 +02:00
let svalue = svalue . to_lowercase ( ) ;
Ok ( Operator ( fid , Equal ( result . ok ( ) , svalue ) ) )
2020-11-26 20:42:54 +01:00
}
fn greater_than (
fields_ids_map : & FieldsIdsMap ,
2021-07-27 16:24:21 +02:00
filterable_fields : & HashSet < String > ,
2020-11-26 20:42:54 +01:00
item : Pair < Rule > ,
2021-06-16 18:33:33 +02:00
) -> Result < FilterCondition > {
2020-11-26 20:42:54 +01:00
let mut items = item . into_inner ( ) ;
2021-07-27 16:24:21 +02:00
let fid = match field_id ( fields_ids_map , filterable_fields , & mut items )
. map_err ( UserError ::InvalidFilterAttribute ) ?
{
Some ( fid ) = > fid ,
None = > return Ok ( Empty ) ,
} ;
2021-05-03 11:45:45 +02:00
2020-11-26 20:42:54 +01:00
let value = items . next ( ) . unwrap ( ) ;
2021-05-03 11:45:45 +02:00
let ( result , _svalue ) = pest_parse ( value ) ;
2021-06-15 17:22:04 +02:00
let value = result . map_err ( UserError ::InvalidFilter ) ? ;
2021-05-03 11:45:45 +02:00
2021-06-14 16:46:19 +02:00
Ok ( Operator ( fid , GreaterThan ( value ) ) )
2020-11-26 20:42:54 +01:00
}
fn greater_than_or_equal (
fields_ids_map : & FieldsIdsMap ,
2021-07-27 16:24:21 +02:00
filterable_fields : & HashSet < String > ,
2020-11-26 20:42:54 +01:00
item : Pair < Rule > ,
2021-06-16 18:33:33 +02:00
) -> Result < FilterCondition > {
2020-11-26 20:42:54 +01:00
let mut items = item . into_inner ( ) ;
2021-07-27 16:24:21 +02:00
let fid = match field_id ( fields_ids_map , filterable_fields , & mut items )
. map_err ( UserError ::InvalidFilterAttribute ) ?
{
Some ( fid ) = > fid ,
None = > return Ok ( Empty ) ,
} ;
2021-05-03 11:45:45 +02:00
2020-11-26 20:42:54 +01:00
let value = items . next ( ) . unwrap ( ) ;
2021-05-03 11:45:45 +02:00
let ( result , _svalue ) = pest_parse ( value ) ;
2021-06-15 17:22:04 +02:00
let value = result . map_err ( UserError ::InvalidFilter ) ? ;
2021-05-03 11:45:45 +02:00
2021-06-14 16:46:19 +02:00
Ok ( Operator ( fid , GreaterThanOrEqual ( value ) ) )
2020-11-26 20:42:54 +01:00
}
fn lower_than (
fields_ids_map : & FieldsIdsMap ,
2021-07-27 16:24:21 +02:00
filterable_fields : & HashSet < String > ,
2020-11-26 20:42:54 +01:00
item : Pair < Rule > ,
2021-06-16 18:33:33 +02:00
) -> Result < FilterCondition > {
2020-11-26 20:42:54 +01:00
let mut items = item . into_inner ( ) ;
2021-07-27 16:24:21 +02:00
let fid = match field_id ( fields_ids_map , filterable_fields , & mut items )
. map_err ( UserError ::InvalidFilterAttribute ) ?
{
Some ( fid ) = > fid ,
None = > return Ok ( Empty ) ,
} ;
2021-05-03 11:45:45 +02:00
2020-11-26 20:42:54 +01:00
let value = items . next ( ) . unwrap ( ) ;
2021-05-03 11:45:45 +02:00
let ( result , _svalue ) = pest_parse ( value ) ;
2021-06-15 17:22:04 +02:00
let value = result . map_err ( UserError ::InvalidFilter ) ? ;
2021-05-03 11:45:45 +02:00
2021-06-14 16:46:19 +02:00
Ok ( Operator ( fid , LowerThan ( value ) ) )
2020-11-26 20:42:54 +01:00
}
fn lower_than_or_equal (
fields_ids_map : & FieldsIdsMap ,
2021-07-27 16:24:21 +02:00
filterable_fields : & HashSet < String > ,
2020-11-26 20:42:54 +01:00
item : Pair < Rule > ,
2021-06-16 18:33:33 +02:00
) -> Result < FilterCondition > {
2020-11-26 20:42:54 +01:00
let mut items = item . into_inner ( ) ;
2021-07-27 16:24:21 +02:00
let fid = match field_id ( fields_ids_map , filterable_fields , & mut items )
. map_err ( UserError ::InvalidFilterAttribute ) ?
{
Some ( fid ) = > fid ,
None = > return Ok ( Empty ) ,
} ;
2021-05-03 11:45:45 +02:00
2020-11-26 20:42:54 +01:00
let value = items . next ( ) . unwrap ( ) ;
2021-05-03 11:45:45 +02:00
let ( result , _svalue ) = pest_parse ( value ) ;
2021-06-15 17:22:04 +02:00
let value = result . map_err ( UserError ::InvalidFilter ) ? ;
2021-05-03 11:45:45 +02:00
2021-06-14 16:46:19 +02:00
Ok ( Operator ( fid , LowerThanOrEqual ( value ) ) )
2020-11-26 20:42:54 +01:00
}
}
2021-06-01 15:25:17 +02:00
impl FilterCondition {
2020-11-26 20:42:54 +01:00
/// Aggregates the documents ids that are part of the specified range automatically
/// going deeper through the levels.
2021-04-07 11:57:16 +02:00
fn explore_facet_number_levels (
rtxn : & heed ::RoTxn ,
db : heed ::Database < FacetLevelValueF64Codec , CboRoaringBitmapCodec > ,
2020-11-26 20:42:54 +01:00
field_id : FieldId ,
level : u8 ,
2021-04-07 11:57:16 +02:00
left : Bound < f64 > ,
right : Bound < f64 > ,
2020-11-26 20:42:54 +01:00
output : & mut RoaringBitmap ,
2021-06-16 18:33:33 +02:00
) -> Result < ( ) > {
2020-11-26 20:42:54 +01:00
match ( left , right ) {
// If the request is an exact value we must go directly to the deepest level.
( Included ( l ) , Included ( r ) ) if l = = r & & level > 0 = > {
2021-06-16 18:33:33 +02:00
return Self ::explore_facet_number_levels (
rtxn , db , field_id , 0 , left , right , output ,
) ;
}
2020-11-26 20:42:54 +01:00
// 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 ( ( ) ) ,
( _ , _ ) = > ( ) ,
}
let mut left_found = None ;
let mut right_found = None ;
// We must create a custom iterator to be able to iterate over the
// requested range as the range iterator cannot express some conditions.
2021-06-23 10:29:00 +02:00
let iter = FacetNumberRange ::new ( rtxn , db , field_id , level , left , right ) ? ;
2020-11-26 20:42:54 +01:00
debug! ( " Iterating between {:?} and {:?} (level {}) " , left , right , level ) ;
for ( i , result ) in iter . enumerate ( ) {
let ( ( _fid , level , l , r ) , docids ) = result ? ;
debug! ( " {:?} to {:?} (level {}) found {} documents " , l , r , level , docids . len ( ) ) ;
2021-06-30 14:12:56 +02:00
* output | = docids ;
2020-11-26 20:42:54 +01:00
// We save the leftest and rightest bounds we actually found at this level.
2021-06-16 18:33:33 +02:00
if i = = 0 {
left_found = Some ( l ) ;
}
2020-11-26 20:42:54 +01:00
right_found = Some ( r ) ;
}
// Can we go deeper?
let deeper_level = match level . checked_sub ( 1 ) {
Some ( level ) = > level ,
None = > return Ok ( ( ) ) ,
} ;
// We must refine the left and right bounds of this range by retrieving the
// missing part in a deeper level.
match left_found . zip ( right_found ) {
Some ( ( left_found , right_found ) ) = > {
// If the bound is satisfied we avoid calling this function again.
if ! matches! ( left , Included ( l ) if l = = left_found ) {
let sub_right = Excluded ( left_found ) ;
2021-06-16 18:33:33 +02:00
debug! (
" calling left with {:?} to {:?} (level {}) " ,
left , sub_right , deeper_level
) ;
Self ::explore_facet_number_levels (
rtxn ,
db ,
field_id ,
deeper_level ,
left ,
sub_right ,
output ,
) ? ;
2020-11-26 20:42:54 +01:00
}
if ! matches! ( right , Included ( r ) if r = = right_found ) {
let sub_left = Excluded ( right_found ) ;
2021-06-16 18:33:33 +02:00
debug! (
" calling right with {:?} to {:?} (level {}) " ,
sub_left , right , deeper_level
) ;
Self ::explore_facet_number_levels (
rtxn ,
db ,
field_id ,
deeper_level ,
sub_left ,
right ,
output ,
) ? ;
2020-11-26 20:42:54 +01:00
}
2021-06-16 18:33:33 +02:00
}
2020-11-26 20:42:54 +01:00
None = > {
// If we found nothing at this level it means that we must find
// the same bounds but at a deeper, more precise level.
2021-06-16 18:33:33 +02:00
Self ::explore_facet_number_levels (
rtxn ,
db ,
field_id ,
deeper_level ,
left ,
right ,
output ,
) ? ;
}
2020-11-26 20:42:54 +01:00
}
Ok ( ( ) )
}
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 ,
2021-05-03 11:45:45 +02:00
numbers_db : heed ::Database < FacetLevelValueF64Codec , CboRoaringBitmapCodec > ,
2021-08-16 13:36:30 +02:00
strings_db : heed ::Database < FacetStringLevelZeroCodec , FacetStringLevelZeroValueCodec > ,
2020-11-26 20:42:54 +01:00
field_id : FieldId ,
2021-05-03 11:45:45 +02:00
operator : & Operator ,
2021-06-16 18:33:33 +02:00
) -> Result < RoaringBitmap > {
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.
let ( left , right ) = match operator {
2021-06-16 18:33:33 +02:00
GreaterThan ( val ) = > ( Excluded ( * val ) , Included ( f64 ::MAX ) ) ,
2021-05-03 11:45:45 +02:00
GreaterThanOrEqual ( val ) = > ( Included ( * val ) , Included ( f64 ::MAX ) ) ,
2021-06-16 18:33:33 +02:00
Equal ( number , string ) = > {
2021-07-17 12:50:01 +02:00
let ( _original_value , string_docids ) =
strings_db . get ( rtxn , & ( field_id , & string ) ) ? . unwrap_or_default ( ) ;
2021-05-03 11:45:45 +02:00
let number_docids = match number {
Some ( n ) = > {
let n = Included ( * n ) ;
let mut output = RoaringBitmap ::new ( ) ;
2021-06-16 18:33:33 +02:00
Self ::explore_facet_number_levels (
rtxn ,
numbers_db ,
field_id ,
0 ,
n ,
n ,
& mut output ,
) ? ;
2021-05-03 11:45:45 +02:00
output
2021-06-16 18:33:33 +02:00
}
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-05-03 11:45:45 +02:00
NotEqual ( number , string ) = > {
let all_numbers_ids = if number . is_some ( ) {
index . number_faceted_documents_ids ( rtxn , field_id ) ?
} else {
RoaringBitmap ::new ( )
} ;
let all_strings_ids = index . string_faceted_documents_ids ( rtxn , field_id ) ? ;
let operator = Equal ( * number , string . clone ( ) ) ;
2021-06-16 18:33:33 +02:00
let docids = Self ::evaluate_operator (
rtxn , index , numbers_db , strings_db , field_id , & operator ,
) ? ;
2021-05-03 11:45:45 +02:00
return Ok ( ( all_numbers_ids | all_strings_ids ) - docids ) ;
2021-06-16 18:33:33 +02:00
}
LowerThan ( val ) = > ( Included ( f64 ::MIN ) , Excluded ( * val ) ) ,
2021-05-03 11:45:45 +02:00
LowerThanOrEqual ( val ) = > ( Included ( f64 ::MIN ) , Included ( * val ) ) ,
2021-06-16 18:33:33 +02:00
Between ( left , right ) = > ( Included ( * left ) , Included ( * right ) ) ,
2021-09-07 12:11:03 +02:00
GeoLowerThan ( base_point , distance ) = > {
2021-08-26 16:38:29 +02:00
let rtree = match index . geo_rtree ( rtxn ) ? {
Some ( rtree ) = > rtree ,
2021-09-09 12:20:08 +02:00
None = > return Ok ( RoaringBitmap ::new ( ) ) ,
2021-08-26 16:38:29 +02:00
} ;
2021-09-09 12:20:08 +02:00
let result = rtree
2021-09-07 12:11:03 +02:00
. nearest_neighbor_iter ( base_point )
. take_while ( | point | {
2021-09-09 12:20:08 +02:00
distance_between_two_points ( base_point , point . geom ( ) ) < * distance
2021-09-07 12:11:03 +02:00
} )
2021-09-09 12:20:08 +02:00
. map ( | point | point . data )
. collect ( ) ;
2021-08-26 16:38:29 +02:00
return Ok ( result ) ;
}
GeoGreaterThan ( point , distance ) = > {
let result = Self ::evaluate_operator (
rtxn ,
index ,
numbers_db ,
strings_db ,
field_id ,
& GeoLowerThan ( point . clone ( ) , * distance ) ,
) ? ;
let geo_faceted_doc_ids = index . geo_faceted_documents_ids ( rtxn ) ? ;
return Ok ( geo_faceted_doc_ids - result ) ;
}
2020-11-26 20:42:54 +01:00
} ;
// Ask for the biggest value that can exist for this specific field, if it exists
// that's fine if it don't, the value just before will be returned instead.
2021-05-03 11:45:45 +02:00
let biggest_level = numbers_db
2021-04-07 11:57:16 +02:00
. remap_data_type ::< DecodeIgnore > ( )
. get_lower_than_or_equal_to ( rtxn , & ( field_id , u8 ::MAX , f64 ::MAX , f64 ::MAX ) ) ?
2020-11-26 20:42:54 +01:00
. and_then ( | ( ( id , level , _ , _ ) , _ ) | if id = = field_id { Some ( level ) } else { None } ) ;
match biggest_level {
Some ( level ) = > {
let mut output = RoaringBitmap ::new ( ) ;
2021-06-16 18:33:33 +02:00
Self ::explore_facet_number_levels (
rtxn ,
numbers_db ,
field_id ,
level ,
left ,
right ,
& mut output ,
) ? ;
2020-11-26 20:42:54 +01:00
Ok ( output )
2021-06-16 18:33:33 +02:00
}
2020-11-26 20:42:54 +01:00
None = > Ok ( RoaringBitmap ::new ( ) ) ,
}
}
2021-06-16 18:33:33 +02:00
pub fn evaluate ( & self , rtxn : & heed ::RoTxn , index : & Index ) -> Result < RoaringBitmap > {
2021-05-03 11:45:45 +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
match self {
2021-05-03 11:45:45 +02:00
Operator ( fid , op ) = > {
Self ::evaluate_operator ( rtxn , index , numbers_db , strings_db , * fid , op )
2021-06-16 18:33:33 +02:00
}
2020-11-26 20:42:54 +01:00
Or ( lhs , rhs ) = > {
let lhs = lhs . evaluate ( rtxn , index ) ? ;
let rhs = rhs . evaluate ( rtxn , index ) ? ;
Ok ( lhs | rhs )
2021-06-16 18:33:33 +02:00
}
2020-11-26 20:42:54 +01:00
And ( lhs , rhs ) = > {
let lhs = lhs . evaluate ( rtxn , index ) ? ;
let rhs = rhs . evaluate ( rtxn , index ) ? ;
Ok ( lhs & rhs )
2021-06-16 18:33:33 +02:00
}
2021-07-27 16:24:21 +02:00
Empty = > Ok ( RoaringBitmap ::new ( ) ) ,
2020-11-26 20:42:54 +01:00
}
}
}
2021-09-15 12:57:18 +02:00
fn field_id_by_key (
fields_ids_map : & FieldsIdsMap ,
filterable_fields : & HashSet < String > ,
key : & str ,
) -> StdResult < Option < FieldId > , IError > {
// lexing ensures that we at least have a key
if ! filterable_fields . contains ( key ) {
return StdResult ::Err ( UserError ::InvalidFilterAttributeNom ) ;
}
Ok ( fields_ids_map . id ( key ) )
}
2021-07-27 16:24:21 +02:00
/// Retrieve the field id base on the pest value.
///
/// Returns an error if the given value is not filterable.
///
/// Returns Ok(None) if the given value is filterable, but is not yet ascociated to a field_id.
2021-06-01 15:22:58 +02:00
///
/// The pest pair is simply a string associated with a span, a location to highlight in
/// the error message.
fn field_id (
fields_ids_map : & FieldsIdsMap ,
2021-07-27 16:24:21 +02:00
filterable_fields : & HashSet < String > ,
2021-06-01 15:22:58 +02:00
items : & mut Pairs < Rule > ,
2021-07-27 16:24:21 +02:00
) -> StdResult < Option < FieldId > , PestError < Rule > > {
2021-06-01 15:22:58 +02:00
// lexing ensures that we at least have a key
let key = items . next ( ) . unwrap ( ) ;
2021-09-06 18:15:20 +02:00
if key . as_rule ( ) = = Rule ::reserved {
2021-09-30 12:28:40 +02:00
let message = match key . as_str ( ) {
key if key . starts_with ( " _geoPoint " ) = > {
format! (
" `_geoPoint` is a reserved keyword and thus can't be used as a filter expression. \
Use the ` _geoRadius ( latitude , longitude , distance ) ` built - in rule to filter on ` _geo ` field coordinates . " ,
)
}
key @ " _geo " = > {
format! (
" `{}` is a reserved keyword and thus can't be used as a filter expression. \
Use the ` _geoRadius ( latitude , longitude , distance ) ` built - in rule to filter on ` _geo ` field coordinates . " ,
key
)
}
key = > format! (
" `{}` is a reserved keyword and thus can't be used as a filter expression. " ,
key
) ,
} ;
return Err ( PestError ::new_from_span ( ErrorVariant ::CustomError { message } , key . as_span ( ) ) ) ;
2021-09-06 18:15:20 +02:00
}
2021-06-01 15:22:58 +02:00
2021-07-27 16:24:21 +02:00
if ! filterable_fields . contains ( key . as_str ( ) ) {
2021-06-01 15:22:58 +02:00
return Err ( PestError ::new_from_span (
ErrorVariant ::CustomError {
message : format ! (
2021-09-30 12:28:40 +02:00
" attribute `{}` is not filterable, available filterable attributes are: {}. " ,
2021-06-01 15:22:58 +02:00
key . as_str ( ) ,
2021-07-27 16:24:21 +02:00
filterable_fields . iter ( ) . join ( " , " ) ,
2021-06-01 15:22:58 +02:00
) ,
} ,
key . as_span ( ) ,
) ) ;
}
2021-07-27 16:24:21 +02:00
Ok ( fields_ids_map . id ( key . as_str ( ) ) )
2021-06-01 15:22:58 +02:00
}
2021-09-15 12:57:18 +02:00
fn nom_parse < T > ( input : & str ) -> ( StdResult < T , UserError > , String )
where
T : FromStr ,
T ::Err : ToString ,
{
let result = match input . parse ::< T > ( ) {
Ok ( value ) = > Ok ( value ) ,
Err ( e ) = > Err ( UserError ::InvalidFilterValue ) ,
} ;
( result , input . to_string ( ) )
}
2021-06-01 15:22:58 +02:00
/// Tries to parse the pest pair into the type `T` specified, always returns
/// the original string that we tried to parse.
///
/// Returns the parsing error associated with the span if the conversion fails.
2021-06-14 16:46:19 +02:00
fn pest_parse < T > ( pair : Pair < Rule > ) -> ( StdResult < T , pest ::error ::Error < Rule > > , String )
2021-06-16 18:33:33 +02:00
where
T : FromStr ,
T ::Err : ToString ,
2021-06-01 15:22:58 +02:00
{
let result = match pair . as_str ( ) . parse ::< T > ( ) {
Ok ( value ) = > Ok ( value ) ,
Err ( e ) = > Err ( PestError ::< Rule > ::new_from_span (
ErrorVariant ::CustomError { message : e . to_string ( ) } ,
pair . as_span ( ) ,
) ) ,
} ;
( result , pair . as_str ( ) . to_string ( ) )
}
2020-11-26 20:42:54 +01:00
#[ cfg(test) ]
mod tests {
2021-06-16 18:33:33 +02:00
use big_s ::S ;
2020-11-26 20:42:54 +01:00
use heed ::EnvOpenOptions ;
2021-05-03 15:58:47 +02:00
use maplit ::hashset ;
2021-06-16 18:33:33 +02:00
use super ::* ;
use crate ::update ::Settings ;
2020-11-26 20:42:54 +01:00
#[ test ]
fn string ( ) {
let path = tempfile ::tempdir ( ) . unwrap ( ) ;
let mut options = EnvOpenOptions ::new ( ) ;
options . map_size ( 10 * 1024 * 1024 ) ; // 10 MB
let index = Index ::new ( options , & path ) . unwrap ( ) ;
2021-06-01 12:19:55 +02:00
// Set the filterable fields to be the channel.
2020-11-26 20:42:54 +01:00
let mut wtxn = index . write_txn ( ) . unwrap ( ) ;
2021-07-27 16:24:21 +02:00
let mut map = index . fields_ids_map ( & wtxn ) . unwrap ( ) ;
map . insert ( " channel " ) ;
index . put_fields_ids_map ( & mut wtxn , & map ) . unwrap ( ) ;
2020-12-22 16:21:07 +01:00
let mut builder = Settings ::new ( & mut wtxn , & index , 0 ) ;
2021-06-16 18:33:33 +02:00
builder . set_filterable_fields ( hashset! { S ( " channel " ) } ) ;
2020-12-22 16:21:07 +01:00
builder . execute ( | _ , _ | ( ) ) . unwrap ( ) ;
2020-11-26 20:42:54 +01:00
wtxn . commit ( ) . unwrap ( ) ;
// Test that the facet condition is correctly generated.
let rtxn = index . read_txn ( ) . unwrap ( ) ;
2021-06-01 15:25:17 +02:00
let condition = FilterCondition ::from_str ( & rtxn , & index , " channel = Ponce " ) . unwrap ( ) ;
2021-05-03 15:58:47 +02:00
let expected = Operator ( 0 , Operator ::Equal ( None , S ( " ponce " ) ) ) ;
2020-11-26 20:42:54 +01:00
assert_eq! ( condition , expected ) ;
2021-06-01 15:25:17 +02:00
let condition = FilterCondition ::from_str ( & rtxn , & index , " channel != ponce " ) . unwrap ( ) ;
2021-05-03 15:58:47 +02:00
let expected = Operator ( 0 , Operator ::NotEqual ( None , S ( " ponce " ) ) ) ;
2020-11-26 20:42:54 +01:00
assert_eq! ( condition , expected ) ;
2021-06-01 15:25:17 +02:00
let condition = FilterCondition ::from_str ( & rtxn , & index , " NOT channel = ponce " ) . unwrap ( ) ;
2021-05-03 15:58:47 +02:00
let expected = Operator ( 0 , Operator ::NotEqual ( None , S ( " ponce " ) ) ) ;
2020-11-26 20:42:54 +01:00
assert_eq! ( condition , expected ) ;
}
#[ test ]
2021-04-07 11:57:16 +02:00
fn number ( ) {
2020-11-26 20:42:54 +01:00
let path = tempfile ::tempdir ( ) . unwrap ( ) ;
let mut options = EnvOpenOptions ::new ( ) ;
options . map_size ( 10 * 1024 * 1024 ) ; // 10 MB
let index = Index ::new ( options , & path ) . unwrap ( ) ;
2021-06-01 12:19:55 +02:00
// Set the filterable fields to be the channel.
2020-11-26 20:42:54 +01:00
let mut wtxn = index . write_txn ( ) . unwrap ( ) ;
2021-07-27 16:24:21 +02:00
let mut map = index . fields_ids_map ( & wtxn ) . unwrap ( ) ;
map . insert ( " timestamp " ) ;
index . put_fields_ids_map ( & mut wtxn , & map ) . unwrap ( ) ;
2020-12-22 16:21:07 +01:00
let mut builder = Settings ::new ( & mut wtxn , & index , 0 ) ;
2021-06-16 18:33:33 +02:00
builder . set_filterable_fields ( hashset! { " timestamp " . into ( ) } ) ;
2020-12-22 16:21:07 +01:00
builder . execute ( | _ , _ | ( ) ) . unwrap ( ) ;
2020-11-26 20:42:54 +01:00
wtxn . commit ( ) . unwrap ( ) ;
// Test that the facet condition is correctly generated.
let rtxn = index . read_txn ( ) . unwrap ( ) ;
2021-06-01 15:25:17 +02:00
let condition = FilterCondition ::from_str ( & rtxn , & index , " timestamp 22 TO 44 " ) . unwrap ( ) ;
2021-05-03 15:58:47 +02:00
let expected = Operator ( 0 , Between ( 22.0 , 44.0 ) ) ;
2020-11-26 20:42:54 +01:00
assert_eq! ( condition , expected ) ;
2021-06-01 15:25:17 +02:00
let condition = FilterCondition ::from_str ( & rtxn , & index , " NOT timestamp 22 TO 44 " ) . unwrap ( ) ;
2021-06-16 18:33:33 +02:00
let expected =
Or ( Box ::new ( Operator ( 0 , LowerThan ( 22.0 ) ) ) , Box ::new ( Operator ( 0 , GreaterThan ( 44.0 ) ) ) ) ;
2020-11-26 20:42:54 +01:00
assert_eq! ( condition , expected ) ;
}
#[ test ]
fn parentheses ( ) {
let path = tempfile ::tempdir ( ) . unwrap ( ) ;
let mut options = EnvOpenOptions ::new ( ) ;
options . map_size ( 10 * 1024 * 1024 ) ; // 10 MB
let index = Index ::new ( options , & path ) . unwrap ( ) ;
2021-06-01 12:19:55 +02:00
// Set the filterable fields to be the channel.
2020-11-26 20:42:54 +01:00
let mut wtxn = index . write_txn ( ) . unwrap ( ) ;
2020-12-22 16:21:07 +01:00
let mut builder = Settings ::new ( & mut wtxn , & index , 0 ) ;
2021-05-03 15:58:47 +02:00
builder . set_searchable_fields ( vec! [ S ( " channel " ) , S ( " timestamp " ) ] ) ; // to keep the fields order
2021-06-16 18:33:33 +02:00
builder . set_filterable_fields ( hashset! { S ( " channel " ) , S ( " timestamp " ) } ) ;
2020-12-22 16:21:07 +01:00
builder . execute ( | _ , _ | ( ) ) . unwrap ( ) ;
2020-11-26 20:42:54 +01:00
wtxn . commit ( ) . unwrap ( ) ;
// Test that the facet condition is correctly generated.
let rtxn = index . read_txn ( ) . unwrap ( ) ;
2021-06-01 15:25:17 +02:00
let condition = FilterCondition ::from_str (
2021-06-16 18:33:33 +02:00
& rtxn ,
& index ,
2020-11-26 20:42:54 +01:00
" channel = gotaga OR (timestamp 22 TO 44 AND channel != ponce) " ,
2021-06-16 18:33:33 +02:00
)
. unwrap ( ) ;
2020-11-26 20:42:54 +01:00
let expected = Or (
2021-05-03 15:58:47 +02:00
Box ::new ( Operator ( 0 , Operator ::Equal ( None , S ( " gotaga " ) ) ) ) ,
2020-11-26 20:42:54 +01:00
Box ::new ( And (
2021-05-03 15:58:47 +02:00
Box ::new ( Operator ( 1 , Between ( 22.0 , 44.0 ) ) ) ,
Box ::new ( Operator ( 0 , Operator ::NotEqual ( None , S ( " ponce " ) ) ) ) ,
2021-06-16 18:33:33 +02:00
) ) ,
2020-11-26 20:42:54 +01:00
) ;
assert_eq! ( condition , expected ) ;
2021-06-01 15:25:17 +02:00
let condition = FilterCondition ::from_str (
2021-06-16 18:33:33 +02:00
& rtxn ,
& index ,
2020-11-26 20:42:54 +01:00
" channel = gotaga OR NOT (timestamp 22 TO 44 AND channel != ponce) " ,
2021-06-16 18:33:33 +02:00
)
. unwrap ( ) ;
2020-11-26 20:42:54 +01:00
let expected = Or (
2021-05-03 15:58:47 +02:00
Box ::new ( Operator ( 0 , Operator ::Equal ( None , S ( " gotaga " ) ) ) ) ,
2020-11-26 20:42:54 +01:00
Box ::new ( Or (
Box ::new ( Or (
2021-05-03 15:58:47 +02:00
Box ::new ( Operator ( 1 , LowerThan ( 22.0 ) ) ) ,
Box ::new ( Operator ( 1 , GreaterThan ( 44.0 ) ) ) ,
2020-11-26 20:42:54 +01:00
) ) ,
2021-05-03 15:58:47 +02:00
Box ::new ( Operator ( 0 , Operator ::Equal ( None , S ( " ponce " ) ) ) ) ,
2020-11-26 20:42:54 +01:00
) ) ,
) ;
assert_eq! ( condition , expected ) ;
}
2020-12-21 19:36:14 +01:00
2021-09-30 12:28:40 +02:00
#[ test ]
fn reserved_field_names ( ) {
let path = tempfile ::tempdir ( ) . unwrap ( ) ;
let mut options = EnvOpenOptions ::new ( ) ;
options . map_size ( 10 * 1024 * 1024 ) ; // 10 MB
let index = Index ::new ( options , & path ) . unwrap ( ) ;
let rtxn = index . read_txn ( ) . unwrap ( ) ;
let error = FilterCondition ::from_str ( & rtxn , & index , " _geo = 12 " ) . unwrap_err ( ) ;
assert! ( error
. to_string ( )
. contains ( " `_geo` is a reserved keyword and thus can't be used as a filter expression. Use the `_geoRadius(latitude, longitude, distance)` built-in rule to filter on `_geo` field coordinates. " ) ,
" {} " ,
error . to_string ( )
) ;
let error =
FilterCondition ::from_str ( & rtxn , & index , r # "_geoDistance <= 1000"# ) . unwrap_err ( ) ;
assert! ( error
. to_string ( )
. contains ( " `_geoDistance` is a reserved keyword and thus can't be used as a filter expression. " ) ,
" {} " ,
error . to_string ( )
) ;
let error = FilterCondition ::from_str ( & rtxn , & index , r # "_geoPoint > 5"# ) . unwrap_err ( ) ;
assert! ( error
. to_string ( )
. contains ( " `_geoPoint` is a reserved keyword and thus can't be used as a filter expression. Use the `_geoRadius(latitude, longitude, distance)` built-in rule to filter on `_geo` field coordinates. " ) ,
" {} " ,
error . to_string ( )
) ;
let error =
FilterCondition ::from_str ( & rtxn , & index , r # "_geoPoint(12, 16) > 5"# ) . unwrap_err ( ) ;
assert! ( error
. to_string ( )
. contains ( " `_geoPoint` is a reserved keyword and thus can't be used as a filter expression. Use the `_geoRadius(latitude, longitude, distance)` built-in rule to filter on `_geo` field coordinates. " ) ,
" {} " ,
error . to_string ( )
) ;
}
2021-09-06 17:07:34 +02:00
#[ test ]
fn geo_radius ( ) {
let path = tempfile ::tempdir ( ) . unwrap ( ) ;
let mut options = EnvOpenOptions ::new ( ) ;
options . map_size ( 10 * 1024 * 1024 ) ; // 10 MB
let index = Index ::new ( options , & path ) . unwrap ( ) ;
// Set the filterable fields to be the channel.
let mut wtxn = index . write_txn ( ) . unwrap ( ) ;
let mut builder = Settings ::new ( & mut wtxn , & index , 0 ) ;
builder . set_searchable_fields ( vec! [ S ( " _geo " ) , S ( " price " ) ] ) ; // to keep the fields order
2021-09-30 12:28:40 +02:00
builder . execute ( | _ , _ | ( ) ) . unwrap ( ) ;
wtxn . commit ( ) . unwrap ( ) ;
let rtxn = index . read_txn ( ) . unwrap ( ) ;
// _geo is not filterable
let result = FilterCondition ::from_str ( & rtxn , & index , " _geoRadius(12, 12, 10) " ) ;
assert! ( result . is_err ( ) ) ;
let error = result . unwrap_err ( ) ;
assert! ( error
. to_string ( )
. contains ( " attribute `_geo` is not filterable, available filterable attributes are: " ) , ) ;
let mut wtxn = index . write_txn ( ) . unwrap ( ) ;
let mut builder = Settings ::new ( & mut wtxn , & index , 0 ) ;
2021-09-06 17:07:34 +02:00
builder . set_filterable_fields ( hashset! { S ( " _geo " ) , S ( " price " ) } ) ;
builder . execute ( | _ , _ | ( ) ) . unwrap ( ) ;
wtxn . commit ( ) . unwrap ( ) ;
let rtxn = index . read_txn ( ) . unwrap ( ) ;
// basic test
let condition =
FilterCondition ::from_str ( & rtxn , & index , " _geoRadius(12, 13.0005, 2000) " ) . unwrap ( ) ;
let expected = Operator ( 0 , GeoLowerThan ( [ 12. , 13.0005 ] , 2000. ) ) ;
assert_eq! ( condition , expected ) ;
2021-10-07 15:42:08 +02:00
// basic test with latitude and longitude at the max angle
let condition =
FilterCondition ::from_str ( & rtxn , & index , " _geoRadius(90, 180, 2000) " ) . unwrap ( ) ;
let expected = Operator ( 0 , GeoLowerThan ( [ 90. , 180. ] , 2000. ) ) ;
assert_eq! ( condition , expected ) ;
// basic test with latitude and longitude at the min angle
let condition =
FilterCondition ::from_str ( & rtxn , & index , " _geoRadius(-90, -180, 2000) " ) . unwrap ( ) ;
let expected = Operator ( 0 , GeoLowerThan ( [ - 90. , - 180. ] , 2000. ) ) ;
assert_eq! ( condition , expected ) ;
2021-09-06 17:07:34 +02:00
// test the negation of the GeoLowerThan
let condition =
FilterCondition ::from_str ( & rtxn , & index , " NOT _geoRadius(50, 18, 2000.500) " ) . unwrap ( ) ;
let expected = Operator ( 0 , GeoGreaterThan ( [ 50. , 18. ] , 2000.500 ) ) ;
assert_eq! ( condition , expected ) ;
// composition of multiple operations
let condition = FilterCondition ::from_str (
& rtxn ,
& index ,
" (NOT _geoRadius(1, 2, 300) AND _geoRadius(1.001, 2.002, 1000.300)) OR price <= 10 " ,
)
. unwrap ( ) ;
let expected = Or (
Box ::new ( And (
Box ::new ( Operator ( 0 , GeoGreaterThan ( [ 1. , 2. ] , 300. ) ) ) ,
Box ::new ( Operator ( 0 , GeoLowerThan ( [ 1.001 , 2.002 ] , 1000.300 ) ) ) ,
) ) ,
Box ::new ( Operator ( 1 , LowerThanOrEqual ( 10. ) ) ) ,
) ;
assert_eq! ( condition , expected ) ;
// georadius don't have any parameters
2021-09-06 17:28:49 +02:00
let result = FilterCondition ::from_str ( & rtxn , & index , " _geoRadius " ) ;
assert! ( result . is_err ( ) ) ;
let error = result . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains ( " The `_geoRadius` filter expect three arguments: `_geoRadius(latitude, longitude, radius)` " ) ) ;
// georadius don't have any parameters
2021-09-06 17:07:34 +02:00
let result = FilterCondition ::from_str ( & rtxn , & index , " _geoRadius() " ) ;
assert! ( result . is_err ( ) ) ;
let error = result . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains ( " The `_geoRadius` filter expect three arguments: `_geoRadius(latitude, longitude, radius)` " ) ) ;
// georadius don't have enough parameters
let result = FilterCondition ::from_str ( & rtxn , & index , " _geoRadius(1, 2) " ) ;
assert! ( result . is_err ( ) ) ;
let error = result . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains ( " The `_geoRadius` filter expect three arguments: `_geoRadius(latitude, longitude, radius)` " ) ) ;
// georadius have too many parameters
let result =
FilterCondition ::from_str ( & rtxn , & index , " _geoRadius(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) " ) ;
assert! ( result . is_err ( ) ) ;
let error = result . unwrap_err ( ) ;
assert! ( error . to_string ( ) . contains ( " The `_geoRadius` filter expect three arguments: `_geoRadius(latitude, longitude, radius)` " ) ) ;
// georadius have a bad latitude
2021-10-07 15:42:08 +02:00
let result = FilterCondition ::from_str ( & rtxn , & index , " _geoRadius(-100, 150, 10) " ) ;
assert! ( result . is_err ( ) ) ;
let error = result . unwrap_err ( ) ;
assert! ( error
. to_string ( )
. contains ( " Latitude must be contained between -90 and 90 degrees. " ) ) ;
// georadius have a bad latitude
let result = FilterCondition ::from_str ( & rtxn , & index , " _geoRadius(-90.0000001, 150, 10) " ) ;
assert! ( result . is_err ( ) ) ;
let error = result . unwrap_err ( ) ;
assert! ( error
. to_string ( )
. contains ( " Latitude must be contained between -90 and 90 degrees. " ) ) ;
// georadius have a bad longitude
let result = FilterCondition ::from_str ( & rtxn , & index , " _geoRadius(-10, 250, 10) " ) ;
2021-09-06 17:07:34 +02:00
assert! ( result . is_err ( ) ) ;
let error = result . unwrap_err ( ) ;
assert! ( error
. to_string ( )
2021-10-07 15:42:08 +02:00
. contains ( " Longitude must be contained between -180 and 180 degrees. " ) ) ;
2021-09-06 17:07:34 +02:00
// georadius have a bad longitude
2021-10-07 15:42:08 +02:00
let result = FilterCondition ::from_str ( & rtxn , & index , " _geoRadius(-10, 180.000001, 10) " ) ;
2021-09-06 17:07:34 +02:00
assert! ( result . is_err ( ) ) ;
let error = result . unwrap_err ( ) ;
assert! ( error
. to_string ( )
2021-10-07 15:42:08 +02:00
. contains ( " Longitude must be contained between -180 and 180 degrees. " ) ) ;
2021-09-06 17:07:34 +02:00
}
2020-12-21 19:36:14 +01:00
#[ test ]
fn from_array ( ) {
let path = tempfile ::tempdir ( ) . unwrap ( ) ;
let mut options = EnvOpenOptions ::new ( ) ;
options . map_size ( 10 * 1024 * 1024 ) ; // 10 MB
let index = Index ::new ( options , & path ) . unwrap ( ) ;
2021-06-01 12:19:55 +02:00
// Set the filterable fields to be the channel.
2020-12-21 19:36:14 +01:00
let mut wtxn = index . write_txn ( ) . unwrap ( ) ;
2021-02-01 14:38:04 +01:00
let mut builder = Settings ::new ( & mut wtxn , & index , 0 ) ;
2021-05-03 15:58:47 +02:00
builder . set_searchable_fields ( vec! [ S ( " channel " ) , S ( " timestamp " ) ] ) ; // to keep the fields order
2021-06-16 18:33:33 +02:00
builder . set_filterable_fields ( hashset! { S ( " channel " ) , S ( " timestamp " ) } ) ;
2021-02-01 14:38:04 +01:00
builder . execute ( | _ , _ | ( ) ) . unwrap ( ) ;
2020-12-21 19:36:14 +01:00
wtxn . commit ( ) . unwrap ( ) ;
// Test that the facet condition is correctly generated.
let rtxn = index . read_txn ( ) . unwrap ( ) ;
2021-06-01 15:25:17 +02:00
let condition = FilterCondition ::from_array (
2021-06-16 18:33:33 +02:00
& rtxn ,
& index ,
vec! [
Either ::Right ( " channel = gotaga " ) ,
Either ::Left ( vec! [ " timestamp = 44 " , " channel != ponce " ] ) ,
] ,
)
. unwrap ( )
. unwrap ( ) ;
2021-06-01 15:25:17 +02:00
let expected = FilterCondition ::from_str (
2021-06-16 18:33:33 +02:00
& rtxn ,
& index ,
2020-12-21 19:36:14 +01:00
" channel = gotaga AND (timestamp = 44 OR channel != ponce) " ,
2021-06-16 18:33:33 +02:00
)
. unwrap ( ) ;
2020-12-21 19:36:14 +01:00
assert_eq! ( condition , expected ) ;
}
2020-11-26 20:42:54 +01:00
}