attribute-search-engine 0.4.0

Generic search engine for rows with attributes using different matchers.
Documentation
use std::collections::HashSet;

use attribute_search_engine::*;

#[test]
fn query_btree_range_index() {
    let engine = create_person_search_engine();

    let q = Query::Exact("age".into(), "27".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![0, 1,])));

    let q = Query::InRange("age".into(), "24".into(), "34".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![0, 1, 3, 4])));

    let q = Query::OutRange("age".into(), "25".into(), "34".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![2, 5])));

    let q = Query::Minimum("age".into(), "27".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![0, 1, 4, 5])));

    let q = Query::Maximum("age".into(), "27".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![0, 1, 2, 3])));
}

#[test]
fn query_hashmap_index() {
    let engine = create_person_search_engine();

    let q = Query::Exact("name".into(), "Bob".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![1])));

    let q = Query::Exact("zipcode".into(), "12345".into());
    assert_eq!(
        engine.search(&q),
        Ok(HashSet::from_iter(vec![0, 1, 2, 4, 5]))
    );

    let q = Query::Exact("city".into(), "Frankfurt".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![])));
}

#[test]
fn query_prefix_tree_index() {
    let engine = create_person_search_engine();

    let q = Query::Exact("permission".into(), "dashboard.show".into());
    assert_eq!(
        engine.search(&q),
        Ok(HashSet::from_iter(vec![0, 1, 2, 3, 4, 5]))
    );

    let q = Query::Exact("permission".into(), "finances".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![])));

    let q = Query::Exact("permission".into(), "personel.read".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![0, 1, 2])));

    let q = Query::Prefix("permission".into(), "finances".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![0, 1, 4, 5])));

    let q = Query::Prefix("permission".into(), "".into());
    assert_eq!(
        engine.search(&q),
        Ok(HashSet::from_iter(vec![0, 1, 2, 3, 4, 5]))
    );

    let q = Query::Prefix("permission".into(), "personel.read".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![0, 1, 2])));

    let q = Query::Prefix("permission".into(), "personel.unknown".into());
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![])));
}

#[test]
fn query_advanced() {
    let engine = create_person_search_engine();

    let q = Query::Exclude(
        Query::And(vec![
            Query::Exact("zipcode".into(), "12345".into()),
            Query::Exact("pet".into(), "Dog".into()),
        ])
        .into(),
        vec![Query::Exact("name".into(), "Hans".into())],
    );
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![1, 5])));

    let q = Query::Exclude(
        Query::Or(vec![
            Query::Exact("zipcode".into(), "12345".into()),
            Query::Exact("pet".into(), "Dog".into()),
        ])
        .into(),
        vec![Query::Exact("name".into(), "Hans".into())],
    );
    assert_eq!(
        engine.search(&q),
        Ok(HashSet::from_iter(vec![0, 1, 2, 3, 5]))
    );
}

#[test]
fn query_parser() {
    let engine = create_person_search_engine();

    let (q, ft) = engine
        .query_from_str("+zipcode:12345 +pet:Dog -name:Hans")
        .expect("valid query");
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![1, 5])));
    assert_eq!(ft, vec![] as Vec<&str>);

    let (q, ft) = engine.query_from_str("+age:27").expect("valid query");
    assert_eq!(engine.search(&q), Ok(HashSet::from_iter(vec![0, 1])));
    assert_eq!(ft, vec![] as Vec<&str>);
}

fn create_person_search_engine() -> SearchEngine<u8> {
    let mut index_name = SearchIndexHashMap::<_, String>::new();
    let mut index_zipcode = SearchIndexHashMap::<_, String>::new();
    let mut index_city = SearchIndexHashMap::<_, String>::new();
    let mut index_pet = SearchIndexHashMap::<_, String>::new();
    let mut index_age = SearchIndexBTreeRange::<_, u8>::new();
    let mut index_permission = SearchIndexPrefixTree::<_>::new();

    index_name.insert(0, "Alice".into());
    index_zipcode.insert(0, "12345".into());
    index_city.insert(0, "New York".into());
    index_age.insert(0, 27);
    index_permission.insert(0, "dashboard.show".into());
    index_permission.insert(0, "finances.read".into());
    index_permission.insert(0, "finances.write".into());
    index_permission.insert(0, "personel.read".into());

    index_name.insert(1, "Bob".into());
    index_zipcode.insert(1, "12345".into());
    index_city.insert(1, "New York".into());
    index_pet.insert(1, "Cat".into());
    index_pet.insert(1, "Dog".into());
    index_pet.insert(1, "Bees".into());
    index_age.insert(1, 27);
    index_permission.insert(1, "dashboard.show".into());
    index_permission.insert(1, "finances.read".into());
    index_permission.insert(1, "personel.read".into());

    index_name.insert(2, "Eve".into());
    index_zipcode.insert(2, "12345".into());
    index_city.insert(2, "New York".into());
    index_zipcode.insert(2, "54321".into());
    index_pet.insert(2, "Cat".into());
    index_city.insert(2, "Berlin".into());
    index_age.insert(2, 23);
    index_permission.insert(2, "dashboard.show".into());
    index_permission.insert(2, "personel.read".into());
    index_permission.insert(2, "personel.write".into());

    index_name.insert(3, "Victor".into());
    index_city.insert(3, "Prag".into());
    index_pet.insert(3, "Dog".into());
    index_age.insert(3, 25);
    index_permission.insert(3, "dashboard.show".into());

    index_name.insert(4, "Hans".into());
    index_city.insert(4, "New York".into());
    index_zipcode.insert(4, "12345".into());
    index_pet.insert(4, "Dog".into());
    index_age.insert(4, 34);
    index_permission.insert(4, "dashboard.show".into());
    index_permission.insert(4, "finances.read".into());

    index_name.insert(5, "Peter".into());
    index_city.insert(5, "New York".into());
    index_zipcode.insert(5, "12345".into());
    index_pet.insert(5, "Dog".into());
    index_pet.insert(5, "Cat".into());
    index_age.insert(5, 51);
    index_permission.insert(5, "dashboard.show".into());
    index_permission.insert(5, "finances.read".into());
    index_permission.insert(5, "finances.write".into());
    index_permission.insert(5, "finances.share".into());

    let mut engine = SearchEngine::new();
    engine.add_index("name", index_name);
    engine.add_index("zipcode", index_zipcode);
    engine.add_index("city", index_city);
    engine.add_index("pet", index_pet);
    engine.add_index("age", index_age);
    engine.add_index("permission", index_permission);

    engine
}