1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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> {
    ///represents a filter for the Entity
    pub entity: EntityFilter<'a>,
    ///represents a filter for the Attribute
    pub attribute: AttributeFilter<'a, A>,
    ///represents a filter for the Value
    pub value: ValueFilter<'a>,
    ///For this query system we are able to provide a tombstone set on the query level which allows us to specify which Attribute match should take precedent over the others.
    ///This is useful for some of the Link CRDT operations we are doing. Note that if no tombstone is found, the latest entry is returned
    ///from the subset that is obtained. The tombstone is optional so if it is not supplied, no tombstone check will be done.
    ///Currently the Tombstone does not work on an IndexByRange IndexFilter and will operate as if the tombstone was not set
    pub tombstone: Option<AttributeFilter<'a, A>>,
    ///represents a filter for the Index
    pub 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,
            None,
        )
    }
}

/// Creates a query for the EAVI system
impl<'a, A: Attribute> EaviQuery<'a, A> {
    pub fn new(
        entity: EntityFilter<'a>,
        attribute: AttributeFilter<'a, A>,
        value: ValueFilter<'a>,
        index: IndexFilter,
        tombstone: Option<AttributeFilter<'a, A>>,
    ) -> Self {
        Self {
            entity,
            attribute,
            value,
            tombstone,
            index,
        }
    }

    /// This runs the query based the query configuration we have given.
    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
            .filter(|eavi| EaviQuery::eav_check(&eavi, &self.entity, &self.attribute, &self.value));
        match self.index {
            IndexFilter::LatestByAttribute => filtered
                .filter_map(|eavi| {
                    // this fold reduces a set of matched (e,a,v) values but makes sure the tombstone value takes priority.
                    //the starting point is a tuple (Option,bool) which translates to a cnodition in which we have found our tombstone value and it also matches our tombstone
                    let reduced_value =
                        iter2.clone().fold((None, false), |eavi_option, eavi_fold| {
                            //if tombstone match is found, do not check the rest
                            if eavi_option.1 {
                                eavi_option
                            } else {
                                //create eavi query without tombstone set, they are two levels here. we have to make sure that the values match our eav set but later on we check if that value given also matches our tombstone condition
                                let fold_query = EaviQuery::new(
                                    Some(eavi.entity()).into(),
                                    Some(eavi.attribute()).into(),
                                    Some(eavi.value()).into(),
                                    IndexFilter::LatestByAttribute,
                                    None,
                                );
                                //check if the value matches our initial condition
                                if EaviQuery::eav_check(
                                    &eavi_fold,
                                    &fold_query.entity,
                                    &self.attribute,
                                    &fold_query.value,
                                ) {
                                    //check if tombstone condition is met at
                                    //if a tombstone is not found it should default to false
                                    if self
                                        .tombstone()
                                        .as_ref()
                                        .map(|s| s.check(eavi_fold.attribute()))
                                        .unwrap_or_default()
                                    {
                                        //if attrribute is found return the value plus the tombstone boolean set to true
                                        (Some(eavi_fold), true)
                                    } else {
                                        //return value that signifies value has been found but tombstone has not been found
                                        (Some(eavi_fold), false)
                                    }
                                } else {
                                    //if set does not match, just return last value of eavi_option
                                    eavi_option
                                }
                            }
                        });

                    //at the end just return initial value of tombstone
                    reduced_value.0
                })
                .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 fn tombstone(&self) -> &Option<AttributeFilter<'a, A>> {
        &self.tombstone
    }
}

/// Represents a filter type which takes in a function to match on
// pub struct EavFilter<'a, T: 'a + Eq>(Box<dyn Fn(T) -> bool + 'a>);
pub enum EavFilter<'a, T: 'a + Eq> {
    Exact(T),
    Predicate(Box<dyn Fn(T) -> bool + 'a>),
}

impl<'a, T: 'a + Eq> EavFilter<'a, T> {
    pub fn single(val: T) -> Self {
        Self::Exact(val)
    }

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

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

    pub fn check(&self, b: T) -> bool {
        match self {
            Self::Exact(a) => a == &b,
            Self::Predicate(f) => f(b),
        }
    }
}

impl<'a, T: Eq> Default for EavFilter<'a, T> {
    fn default() -> EavFilter<'a, T> {
        Self::Predicate(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>),
}