mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-29 16:24:26 +01:00
Implement the IN filter operator
This commit is contained in:
parent
90a304cb07
commit
ca97cb0eda
@ -6,12 +6,14 @@
|
||||
//! or = and ("OR" WS+ and)*
|
||||
//! and = not ("AND" WS+ not)*
|
||||
//! not = ("NOT" WS+ not) | primary
|
||||
//! primary = (WS* "(" WS* expression WS* ")" WS*) | geoRadius | condition | exists | not_exists | to
|
||||
//! primary = (WS* "(" WS* expression WS* ")" WS*) | geoRadius | in | condition | exists | not_exists | to
|
||||
//! in = value "IN" WS* "[" value_list "]"
|
||||
//! condition = value ("=" | "!=" | ">" | ">=" | "<" | "<=") value
|
||||
//! exists = value "EXISTS"
|
||||
//! not_exists = value "NOT" WS+ "EXISTS"
|
||||
//! to = value value "TO" WS+ value
|
||||
//! value = WS* ( word | singleQuoted | doubleQuoted) WS+
|
||||
//! value_list = (value ("," value)* ","?)?
|
||||
//! singleQuoted = "'" .* all but quotes "'"
|
||||
//! doubleQuoted = "\"" .* all but double quotes "\""
|
||||
//! word = (alphanumeric | _ | - | .)+
|
||||
@ -51,7 +53,7 @@ pub use error::{Error, ErrorKind};
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::{char, multispace0, multispace1};
|
||||
use nom::combinator::{cut, eof, map};
|
||||
use nom::combinator::{cut, eof, map, opt};
|
||||
use nom::multi::{many0, separated_list1};
|
||||
use nom::number::complete::recognize_float;
|
||||
use nom::sequence::{delimited, preceded, terminated, tuple};
|
||||
@ -114,6 +116,7 @@ impl<'a> From<Span<'a>> for Token<'a> {
|
||||
pub enum FilterCondition<'a> {
|
||||
Not(Box<Self>),
|
||||
Condition { fid: Token<'a>, op: Condition<'a> },
|
||||
In { fid: Token<'a>, els: Vec<Token<'a>> },
|
||||
Or(Vec<Self>),
|
||||
And(Vec<Self>),
|
||||
GeoLowerThan { point: [Token<'a>; 2], radius: Token<'a> },
|
||||
@ -161,7 +164,36 @@ fn ws<'a, O>(inner: impl FnMut(Span<'a>) -> IResult<O>) -> impl FnMut(Span<'a>)
|
||||
delimited(multispace0, inner, multispace0)
|
||||
}
|
||||
|
||||
/// or = and ("OR" WS+ and)*
|
||||
|
||||
/// value_list = (value ("," value)* ","?)?
|
||||
fn parse_value_list<'a>(input: Span<'a>) -> IResult<Vec<Token<'a>>> {
|
||||
let (input, first_value) = opt(parse_value)(input)?;
|
||||
if let Some(first_value) = first_value {
|
||||
let value_list_el_parser = preceded(ws(tag(",")), parse_value);
|
||||
|
||||
let (input, mut values) = many0(value_list_el_parser)(input)?;
|
||||
let (input, _) = opt(ws(tag(",")))(input)?;
|
||||
values.insert(0, first_value);
|
||||
|
||||
Ok((input, values))
|
||||
} else {
|
||||
Ok((input, vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
/// in = value "IN" "[" value_list "]"
|
||||
fn parse_in(input: Span) -> IResult<FilterCondition> {
|
||||
let (input, value) = parse_value(input)?;
|
||||
let (input, _) = ws(tag("IN"))(input)?;
|
||||
|
||||
let mut els_parser = delimited(tag("["), parse_value_list, tag("]"));
|
||||
|
||||
let (input, content) = els_parser(input)?;
|
||||
let filter = FilterCondition::In { fid: value, els: content };
|
||||
Ok((input, filter))
|
||||
}
|
||||
|
||||
/// or = and ("OR" and)
|
||||
fn parse_or(input: Span) -> IResult<FilterCondition> {
|
||||
let (input, first_filter) = parse_and(input)?;
|
||||
// if we found a `OR` then we MUST find something next
|
||||
@ -257,6 +289,7 @@ fn parse_primary(input: Span) -> IResult<FilterCondition> {
|
||||
}),
|
||||
),
|
||||
parse_geo_radius,
|
||||
parse_in,
|
||||
parse_condition,
|
||||
parse_exists,
|
||||
parse_not_exists,
|
||||
@ -297,6 +330,47 @@ pub mod tests {
|
||||
|
||||
let test_case = [
|
||||
// simple test
|
||||
(
|
||||
"colour IN[]",
|
||||
Fc::In {
|
||||
fid: rtok("", "colour"),
|
||||
els: vec![]
|
||||
}
|
||||
),
|
||||
(
|
||||
"colour IN[green]",
|
||||
Fc::In {
|
||||
fid: rtok("", "colour"),
|
||||
els: vec![rtok("colour IN[", "green")]
|
||||
}
|
||||
),
|
||||
(
|
||||
"colour IN[green,]",
|
||||
Fc::In {
|
||||
fid: rtok("", "colour"),
|
||||
els: vec![rtok("colour IN[", "green")]
|
||||
}
|
||||
),
|
||||
(
|
||||
"colour IN[green,blue]",
|
||||
Fc::In {
|
||||
fid: rtok("", "colour"),
|
||||
els: vec![
|
||||
rtok("colour IN[", "green"),
|
||||
rtok("colour IN[green, ", "blue"),
|
||||
]
|
||||
}
|
||||
),
|
||||
(
|
||||
" colour IN [ green , blue , ]",
|
||||
Fc::In {
|
||||
fid: rtok(" ", "colour"),
|
||||
els: vec![
|
||||
rtok("colour IN [ ", "green"),
|
||||
rtok("colour IN [ green , ", "blue"),
|
||||
]
|
||||
}
|
||||
),
|
||||
(
|
||||
"channel = Ponce",
|
||||
Fc::Condition {
|
||||
|
@ -362,6 +362,32 @@ impl<'a> Filter<'a> {
|
||||
)?;
|
||||
return Ok(all_ids - selected);
|
||||
}
|
||||
FilterCondition::In { fid, els } => {
|
||||
// TODO: this could be optimised
|
||||
let filterable_fields = index.filterable_fields(rtxn)?;
|
||||
|
||||
if crate::is_faceted(fid.value(), &filterable_fields) {
|
||||
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 {
|
||||
return Err(fid.as_external_error(FilterError::AttributeNotFilterable {
|
||||
attribute: fid.value(),
|
||||
filterable_fields,
|
||||
}))?;
|
||||
}
|
||||
}
|
||||
FilterCondition::Condition { fid, op } => {
|
||||
if crate::is_faceted(fid.value(), filterable_fields) {
|
||||
let field_ids_map = index.fields_ids_map(rtxn)?;
|
||||
|
Loading…
Reference in New Issue
Block a user