Apply PR requests related to Refactor search and facet-search

This commit is contained in:
ManyTheFish 2025-03-05 15:42:10 +01:00
parent 63e753bde0
commit 67f7470c83
5 changed files with 111 additions and 80 deletions

View File

@ -894,7 +894,7 @@ async fn search_with_pattern_filter_settings_errors() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###" snapshot!(json_string!(response), @r###"
{ {
"message": "Index `test`: Filter operator `=` is not allowed for the attribute `cattos`, allowed operators: OR, AND, NOT, <, >, <=, >=, TO, IS EMPTY, IS NULL, EXISTS.", "message": "Index `test`: Filter operator `=` is not allowed for the attribute `cattos`.\n - Note: allowed operators: OR, AND, NOT, <, >, <=, >=, TO, IS EMPTY, IS NULL, EXISTS.\n - Note: field `cattos` matched rule #0 in `filterableAttributes`",
"code": "invalid_search_filter", "code": "invalid_search_filter",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_filter" "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
@ -920,7 +920,7 @@ async fn search_with_pattern_filter_settings_errors() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###" snapshot!(json_string!(response), @r###"
{ {
"message": "Index `test`: Filter operator `=` is not allowed for the attribute `cattos`, allowed operators: OR, AND, NOT, <, >, <=, >=, TO, IS EMPTY, IS NULL, EXISTS.", "message": "Index `test`: Filter operator `=` is not allowed for the attribute `cattos`.\n - Note: allowed operators: OR, AND, NOT, <, >, <=, >=, TO, IS EMPTY, IS NULL, EXISTS.\n - Note: field `cattos` matched rule #0 in `filterableAttributes`",
"code": "invalid_search_filter", "code": "invalid_search_filter",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_filter" "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
@ -941,7 +941,7 @@ async fn search_with_pattern_filter_settings_errors() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###" snapshot!(json_string!(response), @r###"
{ {
"message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`, allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.", "message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`.\n - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n - Note: field `doggos.age` matched rule #0 in `filterableAttributes`",
"code": "invalid_search_filter", "code": "invalid_search_filter",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_filter" "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
@ -967,7 +967,7 @@ async fn search_with_pattern_filter_settings_errors() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###" snapshot!(json_string!(response), @r###"
{ {
"message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`, allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.", "message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`.\n - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n - Note: field `doggos.age` matched rule #0 in `filterableAttributes`",
"code": "invalid_search_filter", "code": "invalid_search_filter",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_filter" "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
@ -993,7 +993,7 @@ async fn search_with_pattern_filter_settings_errors() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###" snapshot!(json_string!(response), @r###"
{ {
"message": "Index `test`: Filter operator `TO` is not allowed for the attribute `doggos.age`, allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.", "message": "Index `test`: Filter operator `TO` is not allowed for the attribute `doggos.age`.\n - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n - Note: field `doggos.age` matched rule #0 in `filterableAttributes`",
"code": "invalid_search_filter", "code": "invalid_search_filter",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_filter" "link": "https://docs.meilisearch.com/errors#invalid_search_filter"

View File

@ -335,7 +335,7 @@ async fn search_with_pattern_filter_settings_scenario_1() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###" snapshot!(json_string!(response), @r###"
{ {
"message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`, allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.", "message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`.\n - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n - Note: field `doggos.age` matched rule #0 in `filterableAttributes`",
"code": "invalid_search_filter", "code": "invalid_search_filter",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_filter" "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
@ -481,7 +481,7 @@ async fn search_with_pattern_filter_settings_scenario_1() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###" snapshot!(json_string!(response), @r###"
{ {
"message": "Index `test`: Filter operator `=` is not allowed for the attribute `cattos`, allowed operators: OR, AND, NOT, <, >, <=, >=, TO, IS EMPTY, IS NULL, EXISTS.", "message": "Index `test`: Filter operator `=` is not allowed for the attribute `cattos`.\n - Note: allowed operators: OR, AND, NOT, <, >, <=, >=, TO, IS EMPTY, IS NULL, EXISTS.\n - Note: field `cattos` matched rule #0 in `filterableAttributes`",
"code": "invalid_search_filter", "code": "invalid_search_filter",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_filter" "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
@ -613,7 +613,7 @@ async fn search_with_pattern_filter_settings_scenario_1() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###" snapshot!(json_string!(response), @r###"
{ {
"message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`, allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.", "message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`.\n - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n - Note: field `doggos.age` matched rule #0 in `filterableAttributes`",
"code": "invalid_search_filter", "code": "invalid_search_filter",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_filter" "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
@ -720,7 +720,7 @@ async fn test_filterable_attributes_priority() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###" snapshot!(json_string!(response), @r###"
{ {
"message": "Index `test`: Attribute `doggos.age` is not filterable. Available filterable attributes are: `doggos.age`, `doggos.name`.\n1:11 doggos.age > 2", "message": "Index `test`: Attribute `doggos.age` is not filterable. Available filterable attributes are: `doggos.name`.\n1:11 doggos.age > 2",
"code": "invalid_search_filter", "code": "invalid_search_filter",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_filter" "link": "https://docs.meilisearch.com/errors#invalid_search_filter"

View File

@ -138,8 +138,13 @@ and can not be more than 511 bytes.", .document_id.to_string()
InvalidFilter(String), InvalidFilter(String),
#[error("Invalid type for filter subexpression: expected: {}, found: {}.", .0.join(", "), .1)] #[error("Invalid type for filter subexpression: expected: {}, found: {}.", .0.join(", "), .1)]
InvalidFilterExpression(&'static [&'static str], Value), InvalidFilterExpression(&'static [&'static str], Value),
#[error("Filter operator `{operator}` is not allowed for the attribute `{field}`, allowed operators: {}.", allowed_operators.join(", "))] #[error("Filter operator `{operator}` is not allowed for the attribute `{field}`.\n - Note: allowed operators: {}.\n - Note: field `{field}` matched rule #{rule_index} in `filterableAttributes`", allowed_operators.join(", "))]
FilterOperatorNotAllowed { field: String, allowed_operators: Vec<String>, operator: String }, FilterOperatorNotAllowed {
field: String,
allowed_operators: Vec<String>,
operator: String,
rule_index: usize,
},
#[error("Attribute `{}` is not sortable. {}", #[error("Attribute `{}` is not sortable. {}",
.field, .field,
match .valid_fields.is_empty() { match .valid_fields.is_empty() {

View File

@ -236,19 +236,16 @@ impl Default for FilterFeatures {
pub fn filtered_matching_field_names<'fim>( pub fn filtered_matching_field_names<'fim>(
filterable_attributes: &[FilterableAttributesRule], filterable_attributes: &[FilterableAttributesRule],
fields_ids_map: &'fim FieldsIdsMap, fields_ids_map: &'fim FieldsIdsMap,
filter: &impl Fn(&FilterableAttributesFeatures) -> bool, filter: &impl Fn(FilterableAttributesFeatures) -> bool,
) -> BTreeSet<&'fim str> { ) -> BTreeSet<&'fim str> {
let mut result = BTreeSet::new(); let mut result = BTreeSet::new();
for (_, field_name) in fields_ids_map.iter() { for (_, field_name) in fields_ids_map.iter() {
for filterable_attribute in filterable_attributes { if let Some((_, features)) = matching_features(field_name, filterable_attributes) {
if filterable_attribute.match_str(field_name) == PatternMatch::Match { if filter(features) {
let features = filterable_attribute.features();
if filter(&features) {
result.insert(field_name); result.insert(field_name);
} }
} }
} }
}
result result
} }
@ -260,13 +257,18 @@ pub fn filtered_matching_field_names<'fim>(
/// ///
/// * `field_name` - The field name to match against. /// * `field_name` - The field name to match against.
/// * `filterable_attributes` - The set of filterable attributes rules to match against. /// * `filterable_attributes` - The set of filterable attributes rules to match against.
///
/// # Returns
///
/// * `Some((rule_index, features))` - The features of the matching rule and the index of the rule in the `filterable_attributes` array.
/// * `None` - No matching rule was found.
pub fn matching_features( pub fn matching_features(
field_name: &str, field_name: &str,
filterable_attributes: &[FilterableAttributesRule], filterable_attributes: &[FilterableAttributesRule],
) -> Option<FilterableAttributesFeatures> { ) -> Option<(usize, FilterableAttributesFeatures)> {
for filterable_attribute in filterable_attributes { for (id, filterable_attribute) in filterable_attributes.iter().enumerate() {
if filterable_attribute.match_str(field_name) == PatternMatch::Match { if filterable_attribute.match_str(field_name) == PatternMatch::Match {
return Some(filterable_attribute.features()); return Some((id, filterable_attribute.features()));
} }
} }
None None
@ -283,7 +285,7 @@ pub fn is_field_filterable(
filterable_attributes: &[FilterableAttributesRule], filterable_attributes: &[FilterableAttributesRule],
) -> bool { ) -> bool {
matching_features(field_name, filterable_attributes) matching_features(field_name, filterable_attributes)
.map_or(false, |features| features.is_filterable()) .map_or(false, |(_, features)| features.is_filterable())
} }
/// Check if a field is facet searchable calling the method `FilterableAttributesFeatures::is_facet_searchable()`. /// Check if a field is facet searchable calling the method `FilterableAttributesFeatures::is_facet_searchable()`.
@ -297,7 +299,7 @@ pub fn is_field_facet_searchable(
filterable_attributes: &[FilterableAttributesRule], filterable_attributes: &[FilterableAttributesRule],
) -> bool { ) -> bool {
matching_features(field_name, filterable_attributes) matching_features(field_name, filterable_attributes)
.map_or(false, |features| features.is_facet_searchable()) .map_or(false, |(_, features)| features.is_facet_searchable())
} }
/// Match a field against a set of filterable, facet searchable fields, distinct field, sortable fields, and asc_desc fields. /// Match a field against a set of filterable, facet searchable fields, distinct field, sortable fields, and asc_desc fields.
@ -339,7 +341,7 @@ pub fn match_faceted_field(
fn match_pattern_by_features( fn match_pattern_by_features(
field_name: &str, field_name: &str,
filterable_attributes: &[FilterableAttributesRule], filterable_attributes: &[FilterableAttributesRule],
filter: &impl Fn(&FilterableAttributesFeatures) -> bool, filter: &impl Fn(FilterableAttributesFeatures) -> bool,
) -> PatternMatch { ) -> PatternMatch {
let mut selection = PatternMatch::NoMatch; let mut selection = PatternMatch::NoMatch;
@ -353,7 +355,7 @@ fn match_pattern_by_features(
match pattern.match_str(field_name) { match pattern.match_str(field_name) {
PatternMatch::Match => { PatternMatch::Match => {
let features = pattern.features(); let features = pattern.features();
if filter(&features) && can_match { if filter(features) && can_match {
return PatternMatch::Match; return PatternMatch::Match;
} else { } else {
can_match = false; can_match = false;
@ -361,7 +363,7 @@ fn match_pattern_by_features(
} }
PatternMatch::Parent => { PatternMatch::Parent => {
let features = pattern.features(); let features = pattern.features();
if filter(&features) { if filter(features) {
selection = PatternMatch::Parent; selection = PatternMatch::Parent;
} }
} }

View File

@ -20,8 +20,9 @@ use crate::heed_codec::facet::{
}; };
use crate::index::db_name::FACET_ID_STRING_DOCIDS; use crate::index::db_name::FACET_ID_STRING_DOCIDS;
use crate::{ use crate::{
distance_between_two_points, lat_lng_to_xyz, FieldId, FilterableAttributesFeatures, distance_between_two_points, lat_lng_to_xyz, FieldId, FieldsIdsMap,
FilterableAttributesRule, Index, InternalError, Result, SerializationError, FilterableAttributesFeatures, FilterableAttributesRule, Index, InternalError, Result,
SerializationError,
}; };
/// The maximum number of filters the filter AST can process. /// The maximum number of filters the filter AST can process.
@ -233,11 +234,11 @@ impl<'a> Filter<'a> {
impl<'a> Filter<'a> { impl<'a> Filter<'a> {
pub fn evaluate(&self, rtxn: &heed::RoTxn<'_>, index: &Index) -> Result<RoaringBitmap> { 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 // to avoid doing this for each recursive call we're going to do it ONCE ahead of time
let fields_ids_map = index.fields_ids_map(rtxn)?;
let filterable_attributes_rules = index.filterable_attributes_rules(rtxn)?; let filterable_attributes_rules = index.filterable_attributes_rules(rtxn)?;
for fid in self.condition.fids(MAX_FILTER_DEPTH) { for fid in self.condition.fids(MAX_FILTER_DEPTH) {
let attribute = fid.value(); let attribute = fid.value();
if !is_field_filterable(attribute, &filterable_attributes_rules) { if !is_field_filterable(attribute, &filterable_attributes_rules) {
let fields_ids_map = index.fields_ids_map(rtxn)?;
return Err(fid.as_external_error(FilterError::AttributeNotFilterable { return Err(fid.as_external_error(FilterError::AttributeNotFilterable {
attribute, attribute,
filterable_fields: filtered_matching_field_names( filterable_fields: filtered_matching_field_names(
@ -248,7 +249,7 @@ impl<'a> Filter<'a> {
}))?; }))?;
} }
} }
self.inner_evaluate(rtxn, index, &filterable_attributes_rules, None) self.inner_evaluate(rtxn, index, &fields_ids_map, &filterable_attributes_rules, None)
} }
fn evaluate_operator( fn evaluate_operator(
@ -258,6 +259,7 @@ impl<'a> Filter<'a> {
universe: Option<&RoaringBitmap>, universe: Option<&RoaringBitmap>,
operator: &Condition<'a>, operator: &Condition<'a>,
features: &FilterableAttributesFeatures, features: &FilterableAttributesFeatures,
rule_index: usize,
) -> Result<RoaringBitmap> { ) -> Result<RoaringBitmap> {
let numbers_db = index.facet_id_f64_docids; let numbers_db = index.facet_id_f64_docids;
let strings_db = index.facet_id_string_docids; let strings_db = index.facet_id_string_docids;
@ -275,19 +277,29 @@ impl<'a> Filter<'a> {
| Condition::Between { .. } | Condition::Between { .. }
if !features.is_filterable_comparison() => if !features.is_filterable_comparison() =>
{ {
return Err(generate_filter_error(rtxn, index, field_id, operator, features)); return Err(generate_filter_error(
rtxn, index, field_id, operator, features, rule_index,
));
} }
Condition::Empty if !features.is_filterable_empty() => { Condition::Empty if !features.is_filterable_empty() => {
return Err(generate_filter_error(rtxn, index, field_id, operator, features)); return Err(generate_filter_error(
rtxn, index, field_id, operator, features, rule_index,
));
} }
Condition::Null if !features.is_filterable_null() => { Condition::Null if !features.is_filterable_null() => {
return Err(generate_filter_error(rtxn, index, field_id, operator, features)); return Err(generate_filter_error(
rtxn, index, field_id, operator, features, rule_index,
));
} }
Condition::Exists if !features.is_filterable_exists() => { Condition::Exists if !features.is_filterable_exists() => {
return Err(generate_filter_error(rtxn, index, field_id, operator, features)); return Err(generate_filter_error(
rtxn, index, field_id, operator, features, rule_index,
));
} }
Condition::Equal(_) | Condition::NotEqual(_) if !features.is_filterable_equality() => { Condition::Equal(_) | Condition::NotEqual(_) if !features.is_filterable_equality() => {
return Err(generate_filter_error(rtxn, index, field_id, operator, features)); return Err(generate_filter_error(
rtxn, index, field_id, operator, features, rule_index,
));
} }
Condition::GreaterThan(val) => { Condition::GreaterThan(val) => {
(Excluded(val.parse_finite_float()?), Included(f64::MAX)) (Excluded(val.parse_finite_float()?), Included(f64::MAX))
@ -338,8 +350,9 @@ impl<'a> Filter<'a> {
} }
Condition::NotEqual(val) => { Condition::NotEqual(val) => {
let operator = Condition::Equal(val.clone()); let operator = Condition::Equal(val.clone());
let docids = let docids = Self::evaluate_operator(
Self::evaluate_operator(rtxn, index, field_id, None, &operator, features)?; rtxn, index, field_id, None, &operator, features, rule_index,
)?;
let all_ids = index.documents_ids(rtxn)?; let all_ids = index.documents_ids(rtxn)?;
return Ok(all_ids - docids); return Ok(all_ids - docids);
} }
@ -441,7 +454,8 @@ impl<'a> Filter<'a> {
&self, &self,
rtxn: &heed::RoTxn<'_>, rtxn: &heed::RoTxn<'_>,
index: &Index, index: &Index,
filterable_fields: &[FilterableAttributesRule], field_ids_map: &FieldsIdsMap,
filterable_attribute_rules: &[FilterableAttributesRule],
universe: Option<&RoaringBitmap>, universe: Option<&RoaringBitmap>,
) -> Result<RoaringBitmap> { ) -> Result<RoaringBitmap> {
if universe.map_or(false, |u| u.is_empty()) { if universe.map_or(false, |u| u.is_empty()) {
@ -454,7 +468,8 @@ impl<'a> Filter<'a> {
&(f.as_ref().clone()).into(), &(f.as_ref().clone()).into(),
rtxn, rtxn,
index, index,
filterable_fields, field_ids_map,
filterable_attribute_rules,
universe, universe,
)?; )?;
match universe { match universe {
@ -466,15 +481,14 @@ impl<'a> Filter<'a> {
} }
} }
FilterCondition::In { fid, els } => { FilterCondition::In { fid, els } => {
match matching_features(fid.value(), filterable_fields) { match matching_features(fid.value(), filterable_attribute_rules) {
Some(features) if features.is_filterable() => { Some((rule_index, features)) if features.is_filterable() => {
let field_ids_map = index.fields_ids_map(rtxn)?;
if let Some(fid) = field_ids_map.id(fid.value()) { if let Some(fid) = field_ids_map.id(fid.value()) {
els.iter() els.iter()
.map(|el| Condition::Equal(el.clone())) .map(|el| Condition::Equal(el.clone()))
.map(|op| { .map(|op| {
Self::evaluate_operator( Self::evaluate_operator(
rtxn, index, fid, universe, &op, &features, rtxn, index, fid, universe, &op, &features, rule_index,
) )
}) })
.union() .union()
@ -482,46 +496,50 @@ impl<'a> Filter<'a> {
Ok(RoaringBitmap::new()) Ok(RoaringBitmap::new())
} }
} }
_ => { _ => Err(fid.as_external_error(FilterError::AttributeNotFilterable {
let field_ids_map = index.fields_ids_map(rtxn)?;
Err(fid.as_external_error(FilterError::AttributeNotFilterable {
attribute: fid.value(), attribute: fid.value(),
filterable_fields: filtered_matching_field_names( filterable_fields: filtered_matching_field_names(
filterable_fields, filterable_attribute_rules,
&field_ids_map, &field_ids_map,
&|features| features.is_filterable(), &|features| features.is_filterable(),
), ),
}))? }))?,
}
} }
} }
FilterCondition::Condition { fid, op } => { FilterCondition::Condition { fid, op } => {
match matching_features(fid.value(), filterable_fields) { match matching_features(fid.value(), filterable_attribute_rules) {
Some(features) if features.is_filterable() => { Some((rule_index, features)) if features.is_filterable() => {
let field_ids_map = index.fields_ids_map(rtxn)?;
if let Some(fid) = field_ids_map.id(fid.value()) { if let Some(fid) = field_ids_map.id(fid.value()) {
Self::evaluate_operator(rtxn, index, fid, universe, op, &features) Self::evaluate_operator(
rtxn, index, fid, universe, op, &features, rule_index,
)
} else { } else {
Ok(RoaringBitmap::new()) Ok(RoaringBitmap::new())
} }
} }
_ => { _ => Err(fid.as_external_error(FilterError::AttributeNotFilterable {
let field_ids_map = index.fields_ids_map(rtxn)?;
Err(fid.as_external_error(FilterError::AttributeNotFilterable {
attribute: fid.value(), attribute: fid.value(),
filterable_fields: filtered_matching_field_names( filterable_fields: filtered_matching_field_names(
filterable_fields, filterable_attribute_rules,
&field_ids_map, &field_ids_map,
&|features| features.is_filterable(), &|features| features.is_filterable(),
), ),
}))? }))?,
}
} }
} }
FilterCondition::Or(subfilters) => subfilters FilterCondition::Or(subfilters) => subfilters
.iter() .iter()
.cloned() .cloned()
.map(|f| Self::inner_evaluate(&f.into(), rtxn, index, filterable_fields, universe)) .map(|f| {
Self::inner_evaluate(
&f.into(),
rtxn,
index,
field_ids_map,
filterable_attribute_rules,
universe,
)
})
.union(), .union(),
FilterCondition::And(subfilters) => { FilterCondition::And(subfilters) => {
let mut subfilters_iter = subfilters.iter(); let mut subfilters_iter = subfilters.iter();
@ -530,7 +548,8 @@ impl<'a> Filter<'a> {
&(first_subfilter.clone()).into(), &(first_subfilter.clone()).into(),
rtxn, rtxn,
index, index,
filterable_fields, field_ids_map,
filterable_attribute_rules,
universe, universe,
)?; )?;
for f in subfilters_iter { for f in subfilters_iter {
@ -544,7 +563,8 @@ impl<'a> Filter<'a> {
&(f.clone()).into(), &(f.clone()).into(),
rtxn, rtxn,
index, index,
filterable_fields, field_ids_map,
filterable_attribute_rules,
Some(&bitmap), Some(&bitmap),
)?; )?;
} }
@ -582,11 +602,10 @@ impl<'a> Filter<'a> {
Ok(result) Ok(result)
} else { } else {
let field_ids_map = index.fields_ids_map(rtxn)?;
Err(point[0].as_external_error(FilterError::AttributeNotFilterable { Err(point[0].as_external_error(FilterError::AttributeNotFilterable {
attribute: RESERVED_GEO_FIELD_NAME, attribute: RESERVED_GEO_FIELD_NAME,
filterable_fields: filtered_matching_field_names( filterable_fields: filtered_matching_field_names(
filterable_fields, filterable_attribute_rules,
&field_ids_map, &field_ids_map,
&|features| features.is_filterable(), &|features| features.is_filterable(),
), ),
@ -649,7 +668,8 @@ impl<'a> Filter<'a> {
let selected_lat = Filter { condition: condition_lat }.inner_evaluate( let selected_lat = Filter { condition: condition_lat }.inner_evaluate(
rtxn, rtxn,
index, index,
filterable_fields, field_ids_map,
filterable_attribute_rules,
universe, universe,
)?; )?;
@ -682,7 +702,8 @@ impl<'a> Filter<'a> {
let left = Filter { condition: condition_left }.inner_evaluate( let left = Filter { condition: condition_left }.inner_evaluate(
rtxn, rtxn,
index, index,
filterable_fields, field_ids_map,
filterable_attribute_rules,
universe, universe,
)?; )?;
@ -696,7 +717,8 @@ impl<'a> Filter<'a> {
let right = Filter { condition: condition_right }.inner_evaluate( let right = Filter { condition: condition_right }.inner_evaluate(
rtxn, rtxn,
index, index,
filterable_fields, field_ids_map,
filterable_attribute_rules,
universe, universe,
)?; )?;
@ -712,19 +734,19 @@ impl<'a> Filter<'a> {
Filter { condition: condition_lng }.inner_evaluate( Filter { condition: condition_lng }.inner_evaluate(
rtxn, rtxn,
index, index,
filterable_fields, field_ids_map,
filterable_attribute_rules,
universe, universe,
)? )?
}; };
Ok(selected_lat & selected_lng) Ok(selected_lat & selected_lng)
} else { } else {
let field_ids_map = index.fields_ids_map(rtxn)?;
Err(top_right_point[0].as_external_error( Err(top_right_point[0].as_external_error(
FilterError::AttributeNotFilterable { FilterError::AttributeNotFilterable {
attribute: RESERVED_GEO_FIELD_NAME, attribute: RESERVED_GEO_FIELD_NAME,
filterable_fields: filtered_matching_field_names( filterable_fields: filtered_matching_field_names(
filterable_fields, filterable_attribute_rules,
&field_ids_map, &field_ids_map,
&|features| features.is_filterable(), &|features| features.is_filterable(),
), ),
@ -742,6 +764,7 @@ fn generate_filter_error(
field_id: FieldId, field_id: FieldId,
operator: &Condition<'_>, operator: &Condition<'_>,
features: &FilterableAttributesFeatures, features: &FilterableAttributesFeatures,
rule_index: usize,
) -> Error { ) -> Error {
match index.fields_ids_map(rtxn) { match index.fields_ids_map(rtxn) {
Ok(fields_ids_map) => { Ok(fields_ids_map) => {
@ -750,6 +773,7 @@ fn generate_filter_error(
field: field.to_string(), field: field.to_string(),
allowed_operators: features.allowed_filter_operators(), allowed_operators: features.allowed_filter_operators(),
operator: operator.operator().to_string(), operator: operator.operator().to_string(),
rule_index,
}) })
} }
Err(e) => e.into(), Err(e) => e.into(),