Revert metadata creation when computing the facet-distribution

This commit is contained in:
ManyTheFish 2025-03-10 17:05:41 +01:00
parent abef655849
commit 40c5f911fd
5 changed files with 85 additions and 93 deletions

View File

@ -434,7 +434,7 @@ async fn search_non_filterable_facets() {
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###"
{
"message": "Invalid facet distribution, attribute `doggo` is not filterable. The available filterable attribute is `title`.",
"message": "Invalid facet distribution, attribute `doggo` is not filterable. The available filterable attribute pattern is `title`.",
"code": "invalid_search_facets",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_facets"
@ -445,7 +445,7 @@ async fn search_non_filterable_facets() {
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###"
{
"message": "Invalid facet distribution, attribute `doggo` is not filterable. The available filterable attribute is `title`.",
"message": "Invalid facet distribution, attribute `doggo` is not filterable. The available filterable attribute pattern is `title`.",
"code": "invalid_search_facets",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_facets"
@ -468,7 +468,7 @@ async fn search_non_filterable_facets_multiple_filterable() {
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###"
{
"message": "Invalid facet distribution, attribute `doggo` is not filterable. The available filterable attributes are `genres, title`.",
"message": "Invalid facet distribution, attribute `doggo` is not filterable. The available filterable attribute patterns are `genres, title`.",
"code": "invalid_search_facets",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_facets"
@ -479,7 +479,7 @@ async fn search_non_filterable_facets_multiple_filterable() {
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###"
{
"message": "Invalid facet distribution, attribute `doggo` is not filterable. The available filterable attributes are `genres, title`.",
"message": "Invalid facet distribution, attribute `doggo` is not filterable. The available filterable attribute patterns are `genres, title`.",
"code": "invalid_search_facets",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_facets"
@ -532,7 +532,7 @@ async fn search_non_filterable_facets_multiple_facets() {
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###"
{
"message": "Invalid facet distribution, attributes `doggo, neko` are not filterable. The available filterable attributes are `genres, title`.",
"message": "Invalid facet distribution, attributes `doggo, neko` are not filterable. The available filterable attribute patterns are `genres, title`.",
"code": "invalid_search_facets",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_facets"
@ -543,7 +543,7 @@ async fn search_non_filterable_facets_multiple_facets() {
snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###"
{
"message": "Invalid facet distribution, attributes `doggo, neko` are not filterable. The available filterable attributes are `genres, title`.",
"message": "Invalid facet distribution, attributes `doggo, neko` are not filterable. The available filterable attribute patterns are `genres, title`.",
"code": "invalid_search_facets",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_facets"
@ -1204,52 +1204,55 @@ async fn search_on_unknown_field_plus_joker() {
#[actix_rt::test]
async fn distinct_at_search_time() {
let server = Server::new_shared();
let index = server.unique_index();
let server = Server::new().await;
let index = server.index("test");
let (task, _) = index.create(None).await;
index.wait_task(task.uid()).await.succeeded();
let (response, _code) =
index.add_documents(json!([{"id": 1, "color": "Doggo", "machin": "Action"}]), None).await;
index.wait_task(response.uid()).await.succeeded();
let expected_response = json!({
"message": format!("Index `{}`: Attribute `doggo.truc` is not filterable and thus, cannot be used as distinct attribute. This index does not have configured filterable attributes.", index.uid),
let (response, code) =
index.search_post(json!({"page": 0, "hitsPerPage": 2, "distinct": "doggo.truc"})).await;
snapshot!(code, @"400 Bad Request");
snapshot!(response, @r###"
{
"message": "Index `test`: Attribute `doggo.truc` is not filterable and thus, cannot be used as distinct attribute. This index does not have configured filterable attributes.",
"code": "invalid_search_distinct",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_distinct"
});
let (response, code) =
index.search_post(json!({"page": 0, "hitsPerPage": 2, "distinct": "doggo.truc"})).await;
assert_eq!(response, expected_response);
assert_eq!(code, 400);
}
"###);
let (task, _) = index.update_settings_filterable_attributes(json!(["color", "machin"])).await;
index.wait_task(task.uid()).await.succeeded();
let expected_response = json!({
"message": format!("Index `{}`: Attribute `doggo.truc` is not filterable and thus, cannot be used as distinct attribute. Available filterable attributes are: `color, machin`.", index.uid),
let (response, code) =
index.search_post(json!({"page": 0, "hitsPerPage": 2, "distinct": "doggo.truc"})).await;
snapshot!(code, @"400 Bad Request");
snapshot!(response, @r###"
{
"message": "Index `test`: Attribute `doggo.truc` is not filterable and thus, cannot be used as distinct attribute. Available filterable attributes patterns are: `color, machin`.",
"code": "invalid_search_distinct",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_distinct"
});
let (response, code) =
index.search_post(json!({"page": 0, "hitsPerPage": 2, "distinct": "doggo.truc"})).await;
assert_eq!(response, expected_response);
assert_eq!(code, 400);
}
"###);
let (task, _) = index.update_settings_displayed_attributes(json!(["color"])).await;
index.wait_task(task.uid()).await.succeeded();
let expected_response = json!({
"message": format!("Index `{}`: Attribute `doggo.truc` is not filterable and thus, cannot be used as distinct attribute. Available filterable attributes are: `color, <..hidden-attributes>`.", index.uid),
let (response, code) =
index.search_post(json!({"page": 0, "hitsPerPage": 2, "distinct": "doggo.truc"})).await;
snapshot!(code, @"400 Bad Request");
snapshot!(response, @r###"
{
"message": "Index `test`: Attribute `doggo.truc` is not filterable and thus, cannot be used as distinct attribute. Available filterable attributes patterns are: `color, <..hidden-attributes>`.",
"code": "invalid_search_distinct",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_distinct"
});
let (response, code) =
index.search_post(json!({"page": 0, "hitsPerPage": 2, "distinct": "doggo.truc"})).await;
assert_eq!(response, expected_response);
assert_eq!(code, 400);
}
"###);
let (response, code) =
index.search_post(json!({"page": 0, "hitsPerPage": 2, "distinct": true})).await;

View File

@ -3653,7 +3653,7 @@ async fn federation_non_faceted_for_an_index() {
snapshot!(code, @"400 Bad Request");
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
{
"message": "Inside `.federation.facetsByIndex.fruits-no-name`: Invalid facet distribution, attribute `name` is not filterable. The available filterable attributes are `BOOST, id`.\n - Note: index `fruits-no-name` used in `.queries[1]`",
"message": "Inside `.federation.facetsByIndex.fruits-no-name`: Invalid facet distribution, attribute `name` is not filterable. The available filterable attribute patterns are `BOOST, id`.\n - Note: index `fruits-no-name` used in `.queries[1]`",
"code": "invalid_multi_search_facets",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_facets"
@ -3675,7 +3675,7 @@ async fn federation_non_faceted_for_an_index() {
snapshot!(code, @"400 Bad Request");
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
{
"message": "Inside `.federation.facetsByIndex.fruits-no-name`: Invalid facet distribution, attribute `name` is not filterable. The available filterable attributes are `BOOST, id`.\n - Note: index `fruits-no-name` is not used in queries",
"message": "Inside `.federation.facetsByIndex.fruits-no-name`: Invalid facet distribution, attribute `name` is not filterable. The available filterable attribute patterns are `BOOST, id`.\n - Note: index `fruits-no-name` is not used in queries",
"code": "invalid_multi_search_facets",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_facets"

View File

@ -452,18 +452,19 @@ async fn filter_invalid_attribute_array() {
snapshot!(code, @"202 Accepted");
index.wait_task(value.uid()).await.succeeded();
let expected_response = json!({
"message": "Attribute `many` is not filterable. Available filterable attributes are: `title`.\n1:5 many = Glass",
"code": "invalid_similar_filter",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_similar_filter"
});
index
.similar(
json!({"id": 287947, "filter": ["many = Glass"], "embedder": "manual"}),
|response, code| {
assert_eq!(response, expected_response);
assert_eq!(code, 400);
snapshot!(code, @"400 Bad Request");
snapshot!(response, @r###"
{
"message": "Attribute `many` is not filterable. Available filterable attributes patterns are: `title`.\n1:5 many = Glass",
"code": "invalid_similar_filter",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_similar_filter"
}
"###);
},
)
.await;
@ -492,18 +493,19 @@ async fn filter_invalid_attribute_string() {
snapshot!(code, @"202 Accepted");
index.wait_task(value.uid()).await.succeeded();
let expected_response = json!({
"message": "Attribute `many` is not filterable. Available filterable attributes are: `title`.\n1:5 many = Glass",
"code": "invalid_similar_filter",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_similar_filter"
});
index
.similar(
json!({"id": 287947, "filter": "many = Glass", "embedder": "manual"}),
|response, code| {
assert_eq!(response, expected_response);
assert_eq!(code, 400);
snapshot!(code, @"400 Bad Request");
snapshot!(response, @r###"
{
"message": "Attribute `many` is not filterable. Available filterable attributes patterns are: `title`.\n1:5 many = Glass",
"code": "invalid_similar_filter",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_similar_filter"
}
"###);
},
)
.await;

View File

@ -121,10 +121,10 @@ only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and undersco
and can not be more than 511 bytes.", .document_id.to_string()
)]
InvalidDocumentId { document_id: Value },
#[error("Invalid facet distribution, {}", format_invalid_filter_distribution(.invalid_facets_name, .valid_facets_name))]
#[error("Invalid facet distribution, {}", format_invalid_filter_distribution(.invalid_facets_name, .valid_patterns))]
InvalidFacetsDistribution {
invalid_facets_name: BTreeSet<String>,
valid_facets_name: BTreeSet<String>,
valid_patterns: BTreeSet<String>,
},
#[error(transparent)]
InvalidGeoField(#[from] GeoError),
@ -357,9 +357,9 @@ pub enum GeoError {
fn format_invalid_filter_distribution(
invalid_facets_name: &BTreeSet<String>,
valid_facets_name: &BTreeSet<String>,
valid_patterns: &BTreeSet<String>,
) -> String {
if valid_facets_name.is_empty() {
if valid_patterns.is_empty() {
return "this index does not have configured filterable attributes.".into();
}
@ -381,17 +381,17 @@ fn format_invalid_filter_distribution(
.unwrap(),
};
match valid_facets_name.len() {
match valid_patterns.len() {
1 => write!(
result,
" The available filterable attribute is `{}`.",
valid_facets_name.first().unwrap()
" The available filterable attribute pattern is `{}`.",
valid_patterns.first().unwrap()
)
.unwrap(),
_ => write!(
result,
" The available filterable attributes are `{}`.",
valid_facets_name.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ")
" The available filterable attribute patterns are `{}`.",
valid_patterns.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ")
)
.unwrap(),
}

View File

@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use crate::attribute_patterns::match_field_legacy;
use crate::facet::FacetType;
use crate::fields_ids_map::metadata::{FieldIdMapWithMetadata, Metadata, MetadataBuilder};
use crate::filterable_attributes_rules::{filtered_matching_patterns, matching_features};
use crate::heed_codec::facet::{
FacetGroupKeyCodec, FieldDocIdFacetF64Codec, FieldDocIdFacetStringCodec, OrderedF64Codec,
};
@ -294,13 +294,13 @@ impl<'a> FacetDistribution<'a> {
return Ok(Default::default());
};
let fields_ids_map = self.index.fields_ids_map_with_metadata(self.rtxn)?;
let fields_ids_map = self.index.fields_ids_map(self.rtxn)?;
let filterable_attributes_rules = self.index.filterable_attributes_rules(self.rtxn)?;
self.check_faceted_fields(&fields_ids_map, &filterable_attributes_rules)?;
self.check_faceted_fields(&filterable_attributes_rules)?;
let mut distribution = BTreeMap::new();
for (fid, name, metadata) in fields_ids_map.iter() {
if self.select_field(name, &metadata, &filterable_attributes_rules) {
for (fid, name) in fields_ids_map.iter() {
if self.select_field(name, &filterable_attributes_rules) {
let min_value = if let Some(min_value) = crate::search::facet::facet_min_value(
self.index,
self.rtxn,
@ -331,16 +331,12 @@ impl<'a> FacetDistribution<'a> {
pub fn execute(&self) -> Result<BTreeMap<String, IndexMap<String, u64>>> {
let fields_ids_map = self.index.fields_ids_map(self.rtxn)?;
let fields_ids_map = FieldIdMapWithMetadata::new(
fields_ids_map,
MetadataBuilder::from_index(self.index, self.rtxn)?,
);
let filterable_attributes_rules = self.index.filterable_attributes_rules(self.rtxn)?;
self.check_faceted_fields(&fields_ids_map, &filterable_attributes_rules)?;
self.check_faceted_fields(&filterable_attributes_rules)?;
let mut distribution = BTreeMap::new();
for (fid, name, metadata) in fields_ids_map.iter() {
if self.select_field(name, &metadata, &filterable_attributes_rules) {
for (fid, name) in fields_ids_map.iter() {
if self.select_field(name, &filterable_attributes_rules) {
let order_by = self
.facets
.as_ref()
@ -358,11 +354,12 @@ impl<'a> FacetDistribution<'a> {
fn select_field(
&self,
name: &str,
metadata: &Metadata,
filterable_attributes_rules: &[FilterableAttributesRule],
) -> bool {
// If the field is not filterable, we don't want to compute the facet distribution.
if !metadata.filterable_attributes_features(filterable_attributes_rules).is_filterable() {
if !matching_features(name, filterable_attributes_rules)
.map_or(false, |(_, features)| features.is_filterable())
{
return false;
}
@ -378,41 +375,31 @@ impl<'a> FacetDistribution<'a> {
/// Check if the fields in the facets are valid faceted fields.
fn check_faceted_fields(
&self,
fields_ids_map: &FieldIdMapWithMetadata,
filterable_attributes_rules: &[FilterableAttributesRule],
) -> Result<()> {
let mut invalid_facets = BTreeSet::new();
if let Some(facets) = &self.facets {
for field in facets.keys() {
let is_valid_faceted_field =
fields_ids_map.id_with_metadata(field).map_or(false, |(_, metadata)| {
metadata
.filterable_attributes_features(filterable_attributes_rules)
.is_filterable()
});
if !is_valid_faceted_field {
let is_valid_filterable_field =
matching_features(field, filterable_attributes_rules)
.map_or(false, |(_, features)| features.is_filterable());
if !is_valid_filterable_field {
invalid_facets.insert(field.to_string());
}
}
}
if !invalid_facets.is_empty() {
let valid_facets_name = fields_ids_map
.iter()
.filter_map(|(_, name, metadata)| {
if metadata
.filterable_attributes_features(filterable_attributes_rules)
.is_filterable()
{
Some(name.to_string())
} else {
None
}
let valid_patterns =
filtered_matching_patterns(filterable_attributes_rules, &|features| {
features.is_filterable()
})
.into_iter()
.map(String::from)
.collect();
return Err(Error::UserError(UserError::InvalidFacetsDistribution {
invalid_facets_name: invalid_facets,
valid_facets_name,
valid_patterns,
}));
}