diff --git a/filter-parser/src/condition.rs b/filter-parser/src/condition.rs index 1b15d2daa..679555a89 100644 --- a/filter-parser/src/condition.rs +++ b/filter-parser/src/condition.rs @@ -26,7 +26,7 @@ pub enum Condition<'a> { LowerThan(Token<'a>), LowerThanOrEqual(Token<'a>), Between { from: Token<'a>, to: Token<'a> }, - Contains(Token<'a>), + Contains { keyword: Token<'a>, word: Token<'a> }, } /// condition = value ("==" | ">" ...) value @@ -95,18 +95,29 @@ pub fn parse_not_exists(input: Span) -> IResult { /// contains = value "CONTAINS" value pub fn parse_contains(input: Span) -> IResult { - let (input, (fid, _, value)) = tuple((parse_value, tag("CONTAINS"), cut(parse_value)))(input)?; - Ok((input, FilterCondition::Condition { fid, op: Contains(value) })) + let (input, (fid, contains, value)) = + tuple((parse_value, tag("CONTAINS"), cut(parse_value)))(input)?; + Ok(( + input, + FilterCondition::Condition { + fid, + op: Contains { keyword: Token { span: contains, value: None }, word: value }, + }, + )) } /// contains = value "NOT" WS+ "CONTAINS" value pub fn parse_not_contains(input: Span) -> IResult { let keyword = tuple((tag("NOT"), multispace1, tag("CONTAINS"))); - let (input, (fid, _, value)) = tuple((parse_value, keyword, cut(parse_value)))(input)?; + let (input, (fid, (_not, _spaces, contains), value)) = + tuple((parse_value, keyword, cut(parse_value)))(input)?; Ok(( input, - FilterCondition::Not(Box::new(FilterCondition::Condition { fid, op: Contains(value) })), + FilterCondition::Not(Box::new(FilterCondition::Condition { + fid, + op: Contains { keyword: Token { span: contains, value: None }, word: value }, + })), )) } diff --git a/filter-parser/src/lib.rs b/filter-parser/src/lib.rs index 12847f52b..d06154f25 100644 --- a/filter-parser/src/lib.rs +++ b/filter-parser/src/lib.rs @@ -166,7 +166,7 @@ impl<'a> FilterCondition<'a> { | Condition::LowerThan(_) | Condition::LowerThanOrEqual(_) | Condition::Between { .. } => None, - Condition::Contains(tok) => Some(tok), + Condition::Contains { keyword, word: _ } => Some(keyword), }, FilterCondition::Not(this) => this.use_contains_operator(), FilterCondition::Or(seq) | FilterCondition::And(seq) => { @@ -566,7 +566,7 @@ impl<'a> std::fmt::Display for Condition<'a> { Condition::LowerThan(token) => write!(f, "< {token}"), Condition::LowerThanOrEqual(token) => write!(f, "<= {token}"), Condition::Between { from, to } => write!(f, "{from} TO {to}"), - Condition::Contains(token) => write!(f, "CONTAINS {token}"), + Condition::Contains { word, keyword: _ } => write!(f, "CONTAINS {word}"), } } } diff --git a/meilisearch/tests/search/errors.rs b/meilisearch/tests/search/errors.rs index 29f5791ee..5b15f66ba 100644 --- a/meilisearch/tests/search/errors.rs +++ b/meilisearch/tests/search/errors.rs @@ -1222,7 +1222,7 @@ async fn search_with_contains_without_enabling_the_feature() { snapshot!(code, @"400 Bad Request"); snapshot!(json_string!(response), @r###" { - "message": "Using `CONTAINS` in a filter requires enabling the `contains filter` experimental feature. See https://github.com/orgs/meilisearch/discussions/763\n16:21 doggo CONTAINS kefir", + "message": "Using `CONTAINS` in a filter requires enabling the `contains filter` experimental feature. See https://github.com/orgs/meilisearch/discussions/763\n7:15 doggo CONTAINS kefir", "code": "feature_not_enabled", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#feature_not_enabled" @@ -1235,7 +1235,7 @@ async fn search_with_contains_without_enabling_the_feature() { snapshot!(code, @"400 Bad Request"); snapshot!(json_string!(response), @r###" { - "message": "Using `CONTAINS` in a filter requires enabling the `contains filter` experimental feature. See https://github.com/orgs/meilisearch/discussions/763\n34:39 doggo != echo AND doggo CONTAINS kefir", + "message": "Using `CONTAINS` in a filter requires enabling the `contains filter` experimental feature. See https://github.com/orgs/meilisearch/discussions/763\n25:33 doggo != echo AND doggo CONTAINS kefir", "code": "feature_not_enabled", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#feature_not_enabled" @@ -1251,7 +1251,7 @@ async fn search_with_contains_without_enabling_the_feature() { snapshot!(code, @"400 Bad Request"); snapshot!(json_string!(response), @r###" { - "message": "Using `CONTAINS` in a filter requires enabling the `contains filter` experimental feature. See https://github.com/orgs/meilisearch/discussions/763\n16:21 doggo CONTAINS kefir", + "message": "Using `CONTAINS` in a filter requires enabling the `contains filter` experimental feature. See https://github.com/orgs/meilisearch/discussions/763\n7:15 doggo CONTAINS kefir", "code": "feature_not_enabled", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#feature_not_enabled" @@ -1263,7 +1263,7 @@ async fn search_with_contains_without_enabling_the_feature() { snapshot!(code, @"400 Bad Request"); snapshot!(json_string!(response), @r###" { - "message": "Using `CONTAINS` in a filter requires enabling the `contains filter` experimental feature. See https://github.com/orgs/meilisearch/discussions/763\n16:21 doggo CONTAINS kefir", + "message": "Using `CONTAINS` in a filter requires enabling the `contains filter` experimental feature. See https://github.com/orgs/meilisearch/discussions/763\n7:15 doggo CONTAINS kefir", "code": "feature_not_enabled", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#feature_not_enabled" diff --git a/milli/src/search/facet/filter.rs b/milli/src/search/facet/filter.rs index 08f554b80..e3a99f356 100644 --- a/milli/src/search/facet/filter.rs +++ b/milli/src/search/facet/filter.rs @@ -305,8 +305,8 @@ impl<'a> Filter<'a> { let all_ids = index.documents_ids(rtxn)?; return Ok(all_ids - docids); } - Condition::Contains(val) => { - let value = crate::normalize_facet(val.value()); + Condition::Contains { keyword: _, word } => { + let value = crate::normalize_facet(word.value()); let finder = Finder::new(&value); let base = FacetGroupKey { field_id, level: 0, left_bound: "" }; let docids = strings_db