attribute_search_engine/
lib.rs

1//! Attribute Search Engine is a generic search engine for rows
2//! consisting of attributes, that can be searched using different matchers.
3//!
4//! # Overview
5//! ```rust
6//! use attribute_search_engine::*;
7//! use std::collections::HashSet;
8//!
9//! // Before we can create a new engine we need some indices.
10//! let mut index_name = SearchIndexHashMap::<_, String>::new();
11//! let mut index_age = SearchIndexBTreeRange::<_, u8>::new();
12//! let mut index_address = SearchIndexPrefixTree::<_>::new();
13//!
14//! // We add two persons:
15//! index_name.insert(0, "Alice".into());
16//! index_age.insert(0, 27);
17//! index_address.insert(0, "Germany/Hamburg".into());
18//!
19//! index_name.insert(1, "Bob".into());
20//! index_name.insert(1, "Bobby".into()); // One row can have multiple entries
21//! index_age.insert(1, 25);
22//! index_address.insert(1, "Germany/Berlin".into());
23//!
24//! // Now we create the engine and add our indices to it:
25//! let mut engine = SearchEngine::<usize>::new();
26//! engine.add_index("name", index_name);
27//! engine.add_index("age", index_age);
28//! engine.add_index("address", index_address);
29//!
30//! // We can create queries of any complexity with the Query type.
31//! let q = Query::And(vec![
32//!     Query::Or(vec![
33//!         Query::Exact("name".into(), "Alice".into()),
34//!         Query::Exact("name".into(), "Bob".into()),
35//!     ]),
36//!     Query::Prefix("address".into(), "Germany/".into()),
37//! ]);
38//! assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![0, 1])));
39//!
40//! // The search engine also has the ability to parse strings into
41//! // queries. Check the documentation of SearchEngine::query_from_str
42//! // for more details. Parsed queries are by design a lot more limited
43//! // then manually constructed queries. The construction of queries
44//! // can fail for example if unknown indices are referenced.
45//! let (q, _) = engine.query_from_str("+name:Alice +address:Germany/ -age:25").unwrap();
46//! assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![0])));
47//! ```
48//!
49//! # [Search Indices](SearchIndex)
50//! A SearchIndex saves the mapping from attribute values to primary IDs for a single attribute.
51//! These indices can then be searched using queries. If only a single attribute type is needed,
52//! a SearchIndex by itself may be enough. But normally they are added to a [SearchEngine] that
53//! can handle multiple indices and complex queries involving `Or`, `And` and `Exclude` queries.
54//!
55//! This library provides the following types of search indices:
56//! - [SearchIndexHashMap], backed by a HashMap for quick exact queries.
57//! - [SearchIndexPrefixTree], backed by a prefix tree to find rows just by the prefix of an attribute.
58//! - [SearchIndexBTreeRange], backed by a BTreeMap to find rows with an attribute by providing a range.
59//!
60//! The [SearchEngine] can also work with custom search indices as long as they implement the
61//! [SearchIndex] trait.
62//!
63//! # [Queries](Query)
64//! Queries are used to find rows in a [SearchIndex] or [SearchEngine]. [Query] is an enum type that defines
65//! different search behaviours. Not all Query variants are supported by all index types. Queries can
66//! be crafted manually without restrictions or with some limits from a string using a SearchEngine.
67//!
68//! The following table shows which Query variant is supported by which index type.
69//!
70//! | [Query]                     | [SearchIndexHashMap] | [SearchIndexPrefixTree] | [SearchIndexBTreeRange] |
71//! |-----------------------------|----------------------|-------------------------|-------------------------|
72//! | [Exact](Query::Exact)       | Yes ✔️               | Yes ✔️                 | Yes ✔️                 |
73//! | [Prefix](Query::Prefix)     | No  ❌               | Yes ✔️                 | No  ❌                 |
74//! | [InRange](Query::InRange)   | No  ❌               | No  ❌                 | Yes ✔️                 |
75//! | [OutRange](Query::OutRange) | No  ❌               | No  ❌                 | Yes ✔️                 |
76//! | [Minimum](Query::Minimum)   | No  ❌               | No  ❌                 | Yes ✔️                 |
77//! | [Maximum](Query::Maximum)   | No  ❌               | No  ❌                 | Yes ✔️                 |
78//! | [Or](Query::Or)             | na[^searchengine] 🔷 | na[^searchengine] 🔷   | na[^searchengine] 🔷   |
79//! | [And](Query::And)           | na[^searchengine] 🔷 | na[^searchengine] 🔷   | na[^searchengine] 🔷   |
80//! | [Exclude](Query::Exclude)   | na[^searchengine] 🔷 | na[^searchengine] 🔷   | na[^searchengine] 🔷   |
81//!
82//! [^searchengine]: Or, And & Exclude are only supported by [SearchEngine] and not
83//!                  the indices.
84//!
85//! ## Query String Syntax
86//!
87//! The SearchEngine provides the function `query_from_str` that can be used to create queries
88//! from strings. They are much more limited than manually crafted queries but should be
89//! powerful enough for most use cases.
90//!
91//! The following text is an example for a query:
92//! ```text
93//! +attr1:foo,bar +attr2:=baz +attr3:<42,>84 -attr4:69-121
94//! ```
95//! As a boolean expression it will mean something like this:
96//! ```text
97//!    (attr1==foo || attr1==bar)
98//! && (attr2==baz)
99//! && (attr3 <= 42 || attr3 >= 84)
100//! && !(69 <= attr4 <= 121)
101//! ```
102//!
103//! Are more in-depth description of the query syntax can be found in the documentation of the
104//! [SearchEngine::query_from_str] function.
105//!
106
107mod engine;
108mod error;
109mod index;
110mod query;
111mod query_lexer;
112
113pub use engine::*;
114pub use error::*;
115pub use index::*;
116pub use query::*;