diff --git a/crates/filter-parser/src/lib.rs b/crates/filter-parser/src/lib.rs index 02f338673..1590b08fd 100644 --- a/crates/filter-parser/src/lib.rs +++ b/crates/filter-parser/src/lib.rs @@ -189,6 +189,25 @@ impl<'a> FilterCondition<'a> { } } + pub fn use_vector_filter(&self) -> Option<&Token> { + match self { + FilterCondition::Condition { fid, op: _ } => { + if fid.value().starts_with("_vectors.") || fid.value() == "_vectors" { + Some(fid) + } else { + None + } + } + FilterCondition::Not(this) => this.use_vector_filter(), + FilterCondition::Or(seq) | FilterCondition::And(seq) => { + seq.iter().find_map(|filter| filter.use_vector_filter()) + } + FilterCondition::GeoLowerThan { .. } + | FilterCondition::GeoBoundingBox { .. } + | FilterCondition::In { .. } => None, + } + } + pub fn fids(&self, depth: usize) -> Box + '_> { if depth == 0 { return Box::new(std::iter::empty()); diff --git a/crates/meilisearch/src/search/mod.rs b/crates/meilisearch/src/search/mod.rs index 1c987a70c..e82f4dff7 100644 --- a/crates/meilisearch/src/search/mod.rs +++ b/crates/meilisearch/src/search/mod.rs @@ -2077,7 +2077,7 @@ pub(crate) fn parse_filter( })?; if let Some(ref filter) = filter { - // If the contains operator is used while the contains filter features is not enabled, errors out + // If the contains operator is used while the contains filter feature is not enabled, errors out if let Some((token, error)) = filter.use_contains_operator().zip(features.check_contains_filter().err()) { @@ -2088,6 +2088,18 @@ pub(crate) fn parse_filter( } } + if let Some(ref filter) = filter { + // If a vector filter is used while the multi modal feature is not enabled, errors out + if let Some((token, error)) = + filter.use_vector_filter().zip(features.check_multimodal("using a vector filter").err()) + { + return Err(ResponseError::from_msg( + token.as_external_error(error).to_string(), + Code::FeatureNotEnabled, + )); + } + } + Ok(filter) } diff --git a/crates/meilisearch/tests/search/filters.rs b/crates/meilisearch/tests/search/filters.rs index d0f388220..ff6a0cb17 100644 --- a/crates/meilisearch/tests/search/filters.rs +++ b/crates/meilisearch/tests/search/filters.rs @@ -1068,6 +1068,26 @@ async fn vector_filter_document_template() { "#); } +#[actix_rt::test] +async fn vector_filter_feature_gate() { + let index = shared_index_with_documents().await; + + let (value, _code) = index + .search_post(json!({ + "filter": "_vectors EXISTS", + "attributesToRetrieve": ["name"] + })) + .await; + snapshot!(value, @r#" + { + "message": "using a vector filter requires enabling the `multimodal` experimental feature. See https://github.com/orgs/meilisearch/discussions/846\n1:9 _vectors EXISTS", + "code": "feature_not_enabled", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#feature_not_enabled" + } + "#); +} + #[actix_rt::test] async fn vector_filter_negation() { let index = crate::vector::shared_index_for_fragments().await; diff --git a/crates/milli/src/search/facet/filter.rs b/crates/milli/src/search/facet/filter.rs index 1afdf87e6..21a552965 100644 --- a/crates/milli/src/search/facet/filter.rs +++ b/crates/milli/src/search/facet/filter.rs @@ -228,6 +228,10 @@ impl<'a> Filter<'a> { pub fn use_contains_operator(&self) -> Option<&Token> { self.condition.use_contains_operator() } + + pub fn use_vector_filter(&self) -> Option<&Token> { + self.condition.use_vector_filter() + } } impl<'a> Filter<'a> {