holochain_persistence_api 0.0.1-alpha2

lib3h persistence api for content addressable storage and entity attribute value indexes
use eav::eavi::{Attribute, Entity, EntityAttributeValueIndex, Value};
use std::collections::BTreeSet;

/// Represents a set of filtering operations on the EAVI store.
pub struct EaviQuery<'a, A: Attribute> {
    entity: EntityFilter<'a>,
    attribute: AttributeFilter<'a, A>,
    value: ValueFilter<'a>,
    index: IndexFilter,
}

type EntityFilter<'a> = EavFilter<'a, Entity>;
type AttributeFilter<'a, A> = EavFilter<'a, A>;
type ValueFilter<'a> = EavFilter<'a, Value>;

impl<'a, A: Attribute> Default for EaviQuery<'a, A> {
    fn default() -> EaviQuery<'a, A> {
        EaviQuery::new(
            Default::default(),
            Default::default(),
            Default::default(),
            IndexFilter::LatestByAttribute,
        )
    }
}

impl<'a, A: Attribute> EaviQuery<'a, A> {
    pub fn new(
        entity: EntityFilter<'a>,
        attribute: AttributeFilter<'a, A>,
        value: ValueFilter<'a>,
        index: IndexFilter,
    ) -> Self {
        Self {
            entity,
            attribute,
            value,
            index,
        }
    }

    pub fn run<I>(&self, iter: I) -> BTreeSet<EntityAttributeValueIndex<A>>
    where
        I: Clone + Iterator<Item = EntityAttributeValueIndex<A>> + 'a,
    {
        let iter2 = iter.clone();
        let filtered = iter
            .clone()
            .filter(|eavi| EaviQuery::eav_check(&eavi, &self.entity, &self.attribute, &self.value));

        match self.index {
            IndexFilter::LatestByAttribute => filtered
                .filter(|eavi| {
                    iter2
                        .clone()
                        .filter(|eavi_inner| {
                            EaviQuery::eav_check(
                                &eavi_inner,
                                &Some(eavi.entity()).into(),
                                &self.attribute,
                                &Some(eavi.value()).into(),
                            )
                        })
                        .last()
                        .map(|latest| latest.index() == eavi.index())
                        .unwrap_or(false)
                })
                .collect(),
            IndexFilter::Range(start, end) => filtered
                .filter(|eavi| {
                    start.map(|lo| lo <= eavi.index()).unwrap_or(true)
                        && end.map(|hi| eavi.index() <= hi).unwrap_or(true)
                })
                .collect(),
        }
    }

    fn eav_check(
        eavi: &EntityAttributeValueIndex<A>,
        e: &EntityFilter<'a>,
        a: &AttributeFilter<'a, A>,
        v: &ValueFilter<'a>,
    ) -> bool {
        e.check(eavi.entity()) && a.check(eavi.attribute()) && v.check(eavi.value())
    }

    pub fn entity(&self) -> &EntityFilter<'a> {
        &self.entity
    }
    pub fn attribute(&self) -> &AttributeFilter<'a, A> {
        &self.attribute
    }
    pub fn value(&self) -> &ValueFilter<'a> {
        &self.value
    }
    pub fn index(&self) -> &IndexFilter {
        &self.index
    }
}

pub struct EavFilter<'a, T: 'a + Eq>(Box<dyn Fn(T) -> bool + 'a>);

impl<'a, T: 'a + Eq> EavFilter<'a, T> {
    pub fn single(val: T) -> Self {
        Self(Box::new(move |v| v == val))
    }

    pub fn multiple(vals: Vec<T>) -> Self {
        Self(Box::new(move |val| vals.iter().any(|v| *v == val)))
    }

    pub fn predicate<F>(predicate: F) -> Self
    where
        F: Fn(T) -> bool + 'a,
    {
        Self(Box::new(predicate))
    }

    pub fn check(&self, val: T) -> bool {
        self.0(val)
    }
}

impl<'a, T: Eq> Default for EavFilter<'a, T> {
    fn default() -> EavFilter<'a, T> {
        Self(Box::new(|_| true))
    }
}

impl<'a, T: Eq> From<Option<T>> for EavFilter<'a, T> {
    fn from(val: Option<T>) -> EavFilter<'a, T> {
        val.map(EavFilter::single).unwrap_or_default()
    }
}

impl<'a, T: Eq> From<Vec<T>> for EavFilter<'a, T> {
    fn from(vals: Vec<T>) -> EavFilter<'a, T> {
        EavFilter::multiple(vals)
    }
}

/// Specifies options for filtering on Index:
/// Range returns all results within a particular range of indices.
/// LatestByAttribute is more complex. It first does a normal filter by E, A, and V.
/// Then, for each group of items which differ *only* by Attribute and Index, only the item with
/// highest Index is retained for that grouping.
#[derive(Clone, Debug)]
pub enum IndexFilter {
    LatestByAttribute,
    Range(Option<i64>, Option<i64>),
}