attribute_search_engine/index/
hashmap.rs

1use super::{string_to_payload_type, SearchIndex};
2use crate::{Query, Result, SearchEngineError, SupportedQueries, SUPPORTS_EXACT};
3use std::{
4    collections::{HashMap, HashSet},
5    hash::Hash,
6    str::FromStr,
7};
8
9/// SearchIndexHashMap is a index backed by a HashMap that can match
10/// Exact queries.
11///
12/// # Example
13/// ```
14/// use attribute_search_engine::{SearchIndex, SearchIndexHashMap};
15/// use std::collections::HashSet;
16/// use attribute_search_engine::Query;
17///
18/// let mut index_city = SearchIndexHashMap::<usize, String>::new();
19/// index_city.insert(0, "Berlin".into());
20/// index_city.insert(1, "New York".into());
21/// index_city.insert(2, "Madrid".into());
22///
23/// let result = index_city.search(&Query::Exact("<unused>".into(), "New York".into()));
24/// assert_eq!(result, Ok(HashSet::from_iter(vec![1])));
25/// ```
26pub struct SearchIndexHashMap<P, V> {
27    index: HashMap<V, HashSet<P>>,
28}
29
30impl<P, V> Default for SearchIndexHashMap<P, V>
31where
32    P: Eq + Hash + Clone + 'static,
33    V: Eq + Hash + FromStr + 'static,
34{
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl<P, V> SearchIndexHashMap<P, V>
41where
42    P: Eq + Hash + Clone + 'static,
43    V: Eq + Hash + FromStr + 'static,
44{
45    /// Creates a new `SearchIndexHashMap`.
46    ///
47    /// # Example
48    /// ```rust
49    /// use attribute_search_engine::SearchIndexHashMap;
50    ///
51    /// let index = SearchIndexHashMap::<usize, String>::new();
52    /// ```
53    pub fn new() -> Self {
54        Self {
55            index: HashMap::new(),
56        }
57    }
58
59    /// Insert a new entry in the index.
60    ///
61    /// # Example
62    /// ```rust
63    /// use attribute_search_engine::SearchIndexHashMap;
64    ///
65    /// let mut index = SearchIndexHashMap::<usize, String>::new();
66    ///
67    /// // You insert an entry by giving a row / primary id and an attribute value:
68    /// index.insert(123, "A".into());
69    /// // The same row / primary id can have multiple attributes assigned:
70    /// index.insert(123, "B".into());
71    /// // Add as much entries as you want for as many rows you want:
72    /// index.insert(124, "C".into());
73    /// ```
74    pub fn insert(&mut self, primary_id: P, attribute_value: V) {
75        self.index
76            .entry(attribute_value)
77            .or_default()
78            .insert(primary_id);
79    }
80}
81
82impl<P: Clone, V: Eq + Hash + FromStr> SearchIndex<P> for SearchIndexHashMap<P, V> {
83    fn search(&self, query: &Query) -> Result<HashSet<P>> {
84        match query {
85            Query::Exact(_, value_str) => {
86                let value: V = string_to_payload_type(value_str)?;
87                Ok(self
88                    .index
89                    .get(&value)
90                    .cloned()
91                    .unwrap_or(HashSet::<P>::new()))
92            }
93            _ => Err(SearchEngineError::UnsupportedQuery),
94        }
95    }
96
97    fn supported_queries(&self) -> SupportedQueries {
98        SUPPORTS_EXACT
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn search_index_exact_string() {
108        let mut index = SearchIndexHashMap::<usize, String>::new();
109        index.insert(0, "A".into());
110        index.insert(0, "B".into());
111        index.insert(0, "C".into());
112        index.insert(1, "A".into());
113        index.insert(1, "B".into());
114        index.insert(2, "A".into());
115
116        let result = index.search(&Query::Exact("<not used>".into(), "A".into()));
117        assert_eq!(result, Ok(HashSet::from_iter(vec![0, 1, 2])));
118
119        let result = index.search(&Query::Exact("<not used>".into(), "B".into()));
120        assert_eq!(result, Ok(HashSet::from_iter(vec![0, 1])));
121
122        let result = index.search(&Query::Exact("<not used>".into(), "C".into()));
123        assert_eq!(result, Ok(HashSet::from_iter(vec![0])));
124
125        let result = index.search(&Query::Exact("<not used>".into(), "D".into()));
126        assert_eq!(result, Ok(HashSet::from_iter(vec![])));
127    }
128
129    #[test]
130    fn search_index_exact_number() {
131        let mut index = SearchIndexHashMap::<usize, i32>::new();
132        index.insert(0, 0);
133        index.insert(0, 1);
134        index.insert(0, 2);
135        index.insert(1, 0);
136        index.insert(1, 1);
137        index.insert(2, 0);
138
139        let result = index.search(&Query::Exact("<not used>".into(), "0".into()));
140        assert_eq!(result, Ok(HashSet::from_iter(vec![0, 1, 2])));
141
142        let result = index.search(&Query::Exact("<not used>".into(), "1".into()));
143        assert_eq!(result, Ok(HashSet::from_iter(vec![0, 1])));
144
145        let result = index.search(&Query::Exact("<not used>".into(), "2".into()));
146        assert_eq!(result, Ok(HashSet::from_iter(vec![0])));
147
148        let result = index.search(&Query::Exact("<not used>".into(), "4".into()));
149        assert_eq!(result, Ok(HashSet::from_iter(vec![])));
150    }
151
152    #[test]
153    fn search_index_unsupported_queries() {
154        let mut index = SearchIndexHashMap::<usize, i32>::new();
155        index.insert(0, 0);
156
157        assert_eq!(
158            index.search(&Query::Prefix("<not used>".into(), "0".into())),
159            Err(SearchEngineError::UnsupportedQuery)
160        );
161        assert_eq!(
162            index.search(&Query::InRange("<not used>".into(), "0".into(), "1".into())),
163            Err(SearchEngineError::UnsupportedQuery)
164        );
165        assert_eq!(
166            index.search(&Query::OutRange(
167                "<not used>".into(),
168                "0".into(),
169                "1".into()
170            )),
171            Err(SearchEngineError::UnsupportedQuery)
172        );
173        assert_eq!(
174            index.search(&Query::Minimum("<not used>".into(), "0".into())),
175            Err(SearchEngineError::UnsupportedQuery)
176        );
177        assert_eq!(
178            index.search(&Query::Maximum("<not used>".into(), "0".into())),
179            Err(SearchEngineError::UnsupportedQuery)
180        );
181        assert_eq!(
182            index.search(&Query::Or(vec![])),
183            Err(SearchEngineError::UnsupportedQuery)
184        );
185        assert_eq!(
186            index.search(&Query::And(vec![])),
187            Err(SearchEngineError::UnsupportedQuery)
188        );
189        assert_eq!(
190            index.search(&Query::Exclude(
191                Box::new(Query::Exact("<not used>".into(), "0".into())),
192                vec![]
193            )),
194            Err(SearchEngineError::UnsupportedQuery)
195        );
196    }
197}