diff --git a/milli/src/criterion.rs b/milli/src/criterion.rs index d38450a13..38162a74b 100644 --- a/milli/src/criterion.rs +++ b/milli/src/criterion.rs @@ -12,6 +12,9 @@ pub enum Criterion { Words, /// Sorted by increasing number of typos. Typo, + /// Dynamically sort at query time the documents. None, one or multiple Asc/Desc sortable + /// attributes can be used in place of this criterion at query time. + Sort, /// Sorted by increasing distance between matched query terms. Proximity, /// Documents with quey words contained in more important @@ -38,26 +41,46 @@ impl Criterion { impl FromStr for Criterion { type Err = Error; - fn from_str(txt: &str) -> Result { - match txt { + fn from_str(text: &str) -> Result { + match text { "words" => Ok(Criterion::Words), "typo" => Ok(Criterion::Typo), + "sort" => Ok(Criterion::Sort), "proximity" => Ok(Criterion::Proximity), "attribute" => Ok(Criterion::Attribute), "exactness" => Ok(Criterion::Exactness), - text => match text.rsplit_once(':') { - Some((field_name, "asc")) => Ok(Criterion::Asc(field_name.to_string())), - Some((field_name, "desc")) => Ok(Criterion::Desc(field_name.to_string())), - _ => Err(UserError::InvalidCriterionName { name: text.to_string() }.into()), + text => match AscDesc::from_str(text) { + Ok(AscDesc::Asc(field)) => Ok(Criterion::Asc(field)), + Ok(AscDesc::Desc(field)) => Ok(Criterion::Desc(field)), + Err(error) => Err(error.into()), }, } } } +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub enum AscDesc { + Asc(String), + Desc(String), +} + +impl FromStr for AscDesc { + type Err = UserError; + + fn from_str(text: &str) -> Result { + match text.rsplit_once(':') { + Some((field_name, "asc")) => Ok(AscDesc::Asc(field_name.to_string())), + Some((field_name, "desc")) => Ok(AscDesc::Desc(field_name.to_string())), + _ => Err(UserError::InvalidCriterionName { name: text.to_string() }), + } + } +} + pub fn default_criteria() -> Vec { vec![ Criterion::Words, Criterion::Typo, + Criterion::Sort, Criterion::Proximity, Criterion::Attribute, Criterion::Exactness, @@ -71,6 +94,7 @@ impl fmt::Display for Criterion { match self { Words => f.write_str("words"), Typo => f.write_str("typo"), + Sort => f.write_str("sort"), Proximity => f.write_str("proximity"), Attribute => f.write_str("attribute"), Exactness => f.write_str("exactness"), diff --git a/milli/src/search/criteria/mod.rs b/milli/src/search/criteria/mod.rs index 2ba3b388f..814030c98 100644 --- a/milli/src/search/criteria/mod.rs +++ b/milli/src/search/criteria/mod.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::collections::HashMap; +use std::str::FromStr; use roaring::RoaringBitmap; @@ -273,8 +274,9 @@ impl<'t> CriteriaBuilder<'t> { query_tree: Option, primitive_query: Option>, filtered_candidates: Option, + sort_criteria: Option>, ) -> Result> { - use crate::criterion::Criterion as Name; + use crate::criterion::{AscDesc as AscDescName, Criterion as Name}; let primitive_query = primitive_query.unwrap_or_default(); @@ -282,8 +284,30 @@ impl<'t> CriteriaBuilder<'t> { Box::new(Initial::new(query_tree, filtered_candidates)) as Box; for name in self.index.criteria(&self.rtxn)? { criterion = match name { - Name::Typo => Box::new(Typo::new(self, criterion)), Name::Words => Box::new(Words::new(self, criterion)), + Name::Typo => Box::new(Typo::new(self, criterion)), + Name::Sort => match sort_criteria { + Some(ref sort_criteria) => { + for text in sort_criteria { + criterion = match AscDescName::from_str(text)? { + AscDescName::Asc(field) => Box::new(AscDesc::asc( + &self.index, + &self.rtxn, + criterion, + field, + )?), + AscDescName::Desc(field) => Box::new(AscDesc::desc( + &self.index, + &self.rtxn, + criterion, + field, + )?), + }; + } + criterion + } + None => criterion, + }, Name::Proximity => Box::new(Proximity::new(self, criterion)), Name::Attribute => Box::new(Attribute::new(self, criterion)), Name::Exactness => Box::new(Exactness::new(self, criterion, &primitive_query)?), diff --git a/milli/src/search/mod.rs b/milli/src/search/mod.rs index 871f464ef..43931b6af 100644 --- a/milli/src/search/mod.rs +++ b/milli/src/search/mod.rs @@ -135,7 +135,13 @@ impl<'a> Search<'a> { }; let criteria_builder = criteria::CriteriaBuilder::new(self.rtxn, self.index)?; - let criteria = criteria_builder.build(query_tree, primitive_query, filtered_candidates)?; + let sort_criteria = None; + let criteria = criteria_builder.build( + query_tree, + primitive_query, + filtered_candidates, + sort_criteria, + )?; match self.index.distinct_field(self.rtxn)? { None => self.perform_sort(NoopDistinct, matching_words, criteria), diff --git a/milli/tests/search/mod.rs b/milli/tests/search/mod.rs index c5724a921..b84d3fada 100644 --- a/milli/tests/search/mod.rs +++ b/milli/tests/search/mod.rs @@ -90,6 +90,7 @@ pub fn expected_order( new_groups .extend(group.linear_group_by_key(|d| d.proximity_rank).map(Vec::from)); } + Criterion::Sort => todo!("sort not supported right now"), Criterion::Typo => { group.sort_by_key(|d| d.typo_rank); new_groups.extend(group.linear_group_by_key(|d| d.typo_rank).map(Vec::from));