diff --git a/meilisearch-http/src/analytics/segment_analytics.rs b/meilisearch-http/src/analytics/segment_analytics.rs index b04d814aa..f0dfd0fab 100644 --- a/meilisearch-http/src/analytics/segment_analytics.rs +++ b/meilisearch-http/src/analytics/segment_analytics.rs @@ -366,6 +366,9 @@ pub struct SearchAggregator { // The maximum number of terms in a q request max_terms_number: usize, + // everytime a search is done, we increment the counter linked to the used settings + matching_strategy: HashMap, + // pagination max_limit: usize, max_offset: usize, @@ -423,6 +426,9 @@ impl SearchAggregator { ret.max_terms_number = q.split_whitespace().count(); } + ret.matching_strategy + .insert(format!("{:?}", query.matching_strategy), 1); + ret.max_limit = query.limit; ret.max_offset = query.offset.unwrap_or_default(); @@ -476,6 +482,11 @@ impl SearchAggregator { } // q self.max_terms_number = self.max_terms_number.max(other.max_terms_number); + + for (key, value) in other.matching_strategy.into_iter() { + let matching_strategy = self.matching_strategy.entry(key).or_insert(0); + *matching_strategy = matching_strategy.saturating_add(value); + } // pagination self.max_limit = self.max_limit.max(other.max_limit); self.max_offset = self.max_offset.max(other.max_offset); @@ -517,6 +528,7 @@ impl SearchAggregator { }, "q": { "max_terms_number": self.max_terms_number, + "most_used_matching_strategy": self.matching_strategy.iter().max_by_key(|(_, v)| *v).map(|(k, _)| json!(k)).unwrap_or_else(|| json!(null)), }, "pagination": { "max_limit": self.max_limit, diff --git a/meilisearch-http/src/routes/indexes/search.rs b/meilisearch-http/src/routes/indexes/search.rs index 62bd65e14..973d5eb6e 100644 --- a/meilisearch-http/src/routes/indexes/search.rs +++ b/meilisearch-http/src/routes/indexes/search.rs @@ -2,8 +2,8 @@ use actix_web::{web, HttpRequest, HttpResponse}; use log::debug; use meilisearch_auth::IndexSearchRules; use meilisearch_lib::index::{ - SearchQuery, DEFAULT_CROP_LENGTH, DEFAULT_CROP_MARKER, DEFAULT_HIGHLIGHT_POST_TAG, - DEFAULT_HIGHLIGHT_PRE_TAG, DEFAULT_SEARCH_LIMIT, + MatchingStrategy, SearchQuery, DEFAULT_CROP_LENGTH, DEFAULT_CROP_MARKER, + DEFAULT_HIGHLIGHT_POST_TAG, DEFAULT_HIGHLIGHT_PRE_TAG, DEFAULT_SEARCH_LIMIT, }; use meilisearch_lib::MeiliSearch; use meilisearch_types::error::ResponseError; @@ -45,6 +45,8 @@ pub struct SearchQueryGet { highlight_post_tag: String, #[serde(default = "DEFAULT_CROP_MARKER")] crop_marker: String, + #[serde(default)] + matching_strategy: MatchingStrategy, } impl From for SearchQuery { @@ -76,6 +78,7 @@ impl From for SearchQuery { highlight_pre_tag: other.highlight_pre_tag, highlight_post_tag: other.highlight_post_tag, crop_marker: other.crop_marker, + matching_strategy: other.matching_strategy, } } } diff --git a/meilisearch-lib/src/index/mod.rs b/meilisearch-lib/src/index/mod.rs index 28d6e0222..98c25366d 100644 --- a/meilisearch-lib/src/index/mod.rs +++ b/meilisearch-lib/src/index/mod.rs @@ -1,5 +1,5 @@ pub use search::{ - SearchQuery, SearchResult, DEFAULT_CROP_LENGTH, DEFAULT_CROP_MARKER, + MatchingStrategy, SearchQuery, SearchResult, DEFAULT_CROP_LENGTH, DEFAULT_CROP_MARKER, DEFAULT_HIGHLIGHT_POST_TAG, DEFAULT_HIGHLIGHT_PRE_TAG, DEFAULT_SEARCH_LIMIT, }; pub use updates::{apply_settings_to_builder, Checked, Facets, Settings, Unchecked}; diff --git a/meilisearch-lib/src/index/search.rs b/meilisearch-lib/src/index/search.rs index 97f68b524..5c2882f21 100644 --- a/meilisearch-lib/src/index/search.rs +++ b/meilisearch-lib/src/index/search.rs @@ -7,7 +7,7 @@ use either::Either; use milli::tokenizer::TokenizerBuilder; use milli::{ AscDesc, FieldId, FieldsIdsMap, Filter, FormatOptions, MatchBounds, MatcherBuilder, SortError, - DEFAULT_VALUES_PER_FACET, + TermsMatchingStrategy, DEFAULT_VALUES_PER_FACET, }; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -55,6 +55,32 @@ pub struct SearchQuery { pub highlight_post_tag: String, #[serde(default = "DEFAULT_CROP_MARKER")] pub crop_marker: String, + #[serde(default)] + pub matching_strategy: MatchingStrategy, +} + +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum MatchingStrategy { + // remove last word first + Last, + // all words are mandatory + All, +} + +impl Default for MatchingStrategy { + fn default() -> Self { + Self::Last + } +} + +impl From for TermsMatchingStrategy { + fn from(other: MatchingStrategy) -> Self { + match other { + MatchingStrategy::Last => Self::Last, + MatchingStrategy::All => Self::All, + } + } } #[derive(Debug, Clone, Serialize, PartialEq)] @@ -91,6 +117,8 @@ impl Index { search.query(query); } + search.terms_matching_strategy(query.matching_strategy.into()); + let max_total_hits = self .pagination_max_total_hits(&rtxn)? .unwrap_or(DEFAULT_PAGINATION_MAX_TOTAL_HITS); diff --git a/meilisearch-lib/src/index_controller/mod.rs b/meilisearch-lib/src/index_controller/mod.rs index 3002bfbdd..be855300b 100644 --- a/meilisearch-lib/src/index_controller/mod.rs +++ b/meilisearch-lib/src/index_controller/mod.rs @@ -704,6 +704,7 @@ mod test { highlight_pre_tag: DEFAULT_HIGHLIGHT_PRE_TAG(), highlight_post_tag: DEFAULT_HIGHLIGHT_POST_TAG(), crop_marker: DEFAULT_CROP_MARKER(), + matching_strategy: Default::default(), }; let result = SearchResult {