diff --git a/meilidb/Cargo.toml b/meilidb/Cargo.toml index 8f9517419..e34ccc0eb 100644 --- a/meilidb/Cargo.toml +++ b/meilidb/Cargo.toml @@ -5,6 +5,7 @@ version = "0.3.1" authors = ["Kerollmops "] [dependencies] +meilidb-core = { path = "../meilidb-core", version = "0.1.0" } meilidb-data = { path = "../meilidb-data", version = "0.1.0" } serde = { version = "1.0.91" , features = ["derive"] } serde_json = "1.0.39" @@ -12,7 +13,6 @@ tempfile = "3.0.7" tide = "0.2.0" [dev-dependencies] -meilidb-core = { path = "../meilidb-core", version = "0.1.0" } csv = "1.0.7" env_logger = "0.6.1" jemallocator = "0.1.9" diff --git a/meilidb/src/lib.rs b/meilidb/src/lib.rs new file mode 100644 index 000000000..89ed6b07e --- /dev/null +++ b/meilidb/src/lib.rs @@ -0,0 +1,3 @@ +mod sort_by_attr; + +pub use self::sort_by_attr::SortByAttr; diff --git a/meilidb/src/sort_by_attr.rs b/meilidb/src/sort_by_attr.rs new file mode 100644 index 000000000..6a630bedf --- /dev/null +++ b/meilidb/src/sort_by_attr.rs @@ -0,0 +1,125 @@ +use std::cmp::Ordering; +use std::error::Error; +use std::fmt; + +use meilidb_core::criterion::Criterion; +use meilidb_core::RawDocument; +use meilidb_data::{Schema, SchemaAttr, RankedMap}; + +/// An helper struct that permit to sort documents by +/// some of their stored attributes. +/// +/// # Note +/// +/// If a document cannot be deserialized it will be considered [`None`][]. +/// +/// Deserialized documents are compared like `Some(doc0).cmp(&Some(doc1))`, +/// so you must check the [`Ord`] of `Option` implementation. +/// +/// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None +/// [`Ord`]: https://doc.rust-lang.org/std/option/enum.Option.html#impl-Ord +/// +/// # Example +/// +/// ```ignore +/// use serde_derive::Deserialize; +/// use meilidb::rank::criterion::*; +/// +/// let custom_ranking = SortByAttr::lower_is_better(&ranked_map, &schema, "published_at")?; +/// +/// let builder = CriteriaBuilder::with_capacity(8) +/// .add(SumOfTypos) +/// .add(NumberOfWords) +/// .add(WordsProximity) +/// .add(SumOfWordsAttribute) +/// .add(SumOfWordsPosition) +/// .add(Exact) +/// .add(custom_ranking) +/// .add(DocumentId); +/// +/// let criterion = builder.build(); +/// +/// ``` +pub struct SortByAttr<'a> { + ranked_map: &'a RankedMap, + attr: SchemaAttr, + reversed: bool, +} + +impl<'a> SortByAttr<'a> { + pub fn lower_is_better( + ranked_map: &'a RankedMap, + schema: &Schema, + attr_name: &str, + ) -> Result, SortByAttrError> + { + SortByAttr::new(ranked_map, schema, attr_name, false) + } + + pub fn higher_is_better( + ranked_map: &'a RankedMap, + schema: &Schema, + attr_name: &str, + ) -> Result, SortByAttrError> + { + SortByAttr::new(ranked_map, schema, attr_name, true) + } + + fn new( + ranked_map: &'a RankedMap, + schema: &Schema, + attr_name: &str, + reversed: bool, + ) -> Result, SortByAttrError> + { + let attr = match schema.attribute(attr_name) { + Some(attr) => attr, + None => return Err(SortByAttrError::AttributeNotFound), + }; + + if !schema.props(attr).is_ranked() { + return Err(SortByAttrError::AttributeNotRegisteredForRanking); + } + + Ok(SortByAttr { ranked_map, attr, reversed }) + } +} + +impl<'a> Criterion for SortByAttr<'a> { + fn evaluate(&self, lhs: &RawDocument, rhs: &RawDocument) -> Ordering { + let lhs = self.ranked_map.get(lhs.id, self.attr); + let rhs = self.ranked_map.get(rhs.id, self.attr); + + match (lhs, rhs) { + (Some(lhs), Some(rhs)) => { + let order = lhs.cmp(&rhs); + if self.reversed { order.reverse() } else { order } + }, + (None, Some(_)) => Ordering::Greater, + (Some(_), None) => Ordering::Less, + (None, None) => Ordering::Equal, + } + } + + fn name(&self) -> &'static str { + "SortByAttr" + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SortByAttrError { + AttributeNotFound, + AttributeNotRegisteredForRanking, +} + +impl fmt::Display for SortByAttrError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use SortByAttrError::*; + match self { + AttributeNotFound => f.write_str("attribute not found in the schema"), + AttributeNotRegisteredForRanking => f.write_str("attribute not registered for ranking"), + } + } +} + +impl Error for SortByAttrError { }