Skip to main content

triblespace_core/trible/
tribleset.rs

1mod triblesetconstraint;
2pub mod triblesetrangeconstraint;
3
4use triblesetconstraint::*;
5
6use crate::query::TriblePattern;
7use crate::value::Value;
8
9use crate::patch::Entry;
10use crate::patch::PATCH;
11use crate::query::Variable;
12use crate::trible::AEVOrder;
13use crate::trible::AVEOrder;
14use crate::trible::EAVOrder;
15use crate::trible::EVAOrder;
16use crate::trible::Trible;
17use crate::trible::VAEOrder;
18use crate::trible::VEAOrder;
19use crate::trible::TRIBLE_LEN;
20use crate::value::schemas::genid::GenId;
21use crate::value::ValueSchema;
22
23use std::iter::FromIterator;
24use std::iter::Map;
25use std::ops::Add;
26use std::ops::AddAssign;
27
28/// A collection of [`Trible`]s.
29///
30/// A [`TribleSet`] is a collection of [`Trible`]s that can be queried and manipulated.
31/// It supports efficient set operations like union, intersection, and difference.
32///
33/// The stored [`Trible`]s are indexed by the six possible orderings of their fields
34/// in corresponding [`PATCH`]es.
35///
36/// Clone is extremely cheap and can be used to create a snapshot of the current state of the [`TribleSet`].
37///
38/// Note that the [`TribleSet`] does not support an explicit `delete`/`remove` operation,
39/// as this would conflict with the CRDT semantics of the [`TribleSet`] and CALM principles as a whole.
40/// It does allow for set subtraction, but that operation is meant to compute the difference between two sets
41/// and not to remove elements from the set. A subtle but important distinction.
42#[derive(Debug, Clone)]
43pub struct TribleSet {
44    /// Entity → Attribute → Value index.
45    pub eav: PATCH<TRIBLE_LEN, EAVOrder, ()>,
46    /// Value → Entity → Attribute index.
47    pub vea: PATCH<TRIBLE_LEN, VEAOrder, ()>,
48    /// Attribute → Value → Entity index.
49    pub ave: PATCH<TRIBLE_LEN, AVEOrder, ()>,
50    /// Value → Attribute → Entity index.
51    pub vae: PATCH<TRIBLE_LEN, VAEOrder, ()>,
52    /// Entity → Value → Attribute index.
53    pub eva: PATCH<TRIBLE_LEN, EVAOrder, ()>,
54    /// Attribute → Entity → Value index.
55    pub aev: PATCH<TRIBLE_LEN, AEVOrder, ()>,
56}
57
58/// O(1) fingerprint for a [`TribleSet`], derived from the PATCH root hash.
59///
60/// This matches the equality semantics of [`TribleSet`], but it is not stable
61/// across process boundaries because [`PATCH`] uses a per-process hash key.
62#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
63pub struct TribleSetFingerprint(Option<u128>);
64
65impl TribleSetFingerprint {
66    /// Fingerprint of an empty set.
67    pub const EMPTY: Self = Self(None);
68
69    /// Returns `true` for the empty-set fingerprint.
70    pub fn is_empty(self) -> bool {
71        self.0.is_none()
72    }
73
74    /// Returns the raw 128-bit hash, or `None` for an empty set.
75    pub fn as_u128(self) -> Option<u128> {
76        self.0
77    }
78}
79
80type TribleSetInner<'a> =
81    Map<crate::patch::PATCHIterator<'a, 64, EAVOrder, ()>, fn(&[u8; 64]) -> &Trible>;
82
83/// Iterator over the tribles in a [`TribleSet`], yielded in EAV order.
84pub struct TribleSetIterator<'a> {
85    inner: TribleSetInner<'a>,
86}
87
88impl TribleSet {
89    /// Union of two [`TribleSet`]s.
90    ///
91    /// The other [`TribleSet`] is consumed, and this [`TribleSet`] is updated in place.
92    pub fn union(&mut self, other: Self) {
93        self.eav.union(other.eav);
94        self.eva.union(other.eva);
95        self.aev.union(other.aev);
96        self.ave.union(other.ave);
97        self.vea.union(other.vea);
98        self.vae.union(other.vae);
99    }
100
101    /// Returns a new set containing only tribles present in both sets.
102    pub fn intersect(&self, other: &Self) -> Self {
103        Self {
104            eav: self.eav.intersect(&other.eav),
105            eva: self.eva.intersect(&other.eva),
106            aev: self.aev.intersect(&other.aev),
107            ave: self.ave.intersect(&other.ave),
108            vea: self.vea.intersect(&other.vea),
109            vae: self.vae.intersect(&other.vae),
110        }
111    }
112
113    /// Returns a new set containing tribles in `self` but not in `other`.
114    pub fn difference(&self, other: &Self) -> Self {
115        Self {
116            eav: self.eav.difference(&other.eav),
117            eva: self.eva.difference(&other.eva),
118            aev: self.aev.difference(&other.aev),
119            ave: self.ave.difference(&other.ave),
120            vea: self.vea.difference(&other.vea),
121            vae: self.vae.difference(&other.vae),
122        }
123    }
124
125    /// Creates an empty set.
126    pub fn new() -> TribleSet {
127        TribleSet {
128            eav: PATCH::<TRIBLE_LEN, EAVOrder, ()>::new(),
129            eva: PATCH::<TRIBLE_LEN, EVAOrder, ()>::new(),
130            aev: PATCH::<TRIBLE_LEN, AEVOrder, ()>::new(),
131            ave: PATCH::<TRIBLE_LEN, AVEOrder, ()>::new(),
132            vea: PATCH::<TRIBLE_LEN, VEAOrder, ()>::new(),
133            vae: PATCH::<TRIBLE_LEN, VAEOrder, ()>::new(),
134        }
135    }
136
137    /// Returns the number of tribles in the set.
138    pub fn len(&self) -> usize {
139        self.eav.len() as usize
140    }
141
142    /// Returns `true` when the set contains no tribles.
143    pub fn is_empty(&self) -> bool {
144        self.len() == 0
145    }
146
147    /// Returns a fast fingerprint suitable for in-memory caching.
148    ///
149    /// The fingerprint matches [`TribleSet`] equality, but it is not stable
150    /// across process boundaries because [`PATCH`] uses a per-process hash key.
151    pub fn fingerprint(&self) -> TribleSetFingerprint {
152        TribleSetFingerprint(self.eav.root_hash())
153    }
154
155    /// Inserts a trible into all six covering indexes.
156    pub fn insert(&mut self, trible: &Trible) {
157        let key = Entry::new(&trible.data);
158        self.eav.insert(&key);
159        self.eva.insert(&key);
160        self.aev.insert(&key);
161        self.ave.insert(&key);
162        self.vea.insert(&key);
163        self.vae.insert(&key);
164    }
165
166    /// Returns `true` when the exact trible is present in the set.
167    pub fn contains(&self, trible: &Trible) -> bool {
168        self.eav.has_prefix(&trible.data)
169    }
170
171    /// Creates a constraint that proposes only values in the byte range
172    /// `[min, max]` (inclusive) using the VEA index with `infixes_range`.
173    ///
174    /// Use with `and!` alongside a `pattern!` for efficient range queries:
175    ///
176    /// ```rust,ignore
177    /// find!(ts: Value<NsTAIInterval>,
178    ///     and!(
179    ///         pattern!(&data, [{ ?id @ attr: ?ts }]),
180    ///         data.value_in_range(ts, min_ts, max_ts),
181    ///     )
182    /// )
183    /// ```
184    pub fn value_in_range<V: ValueSchema>(
185        &self,
186        variable: Variable<V>,
187        min: Value<V>,
188        max: Value<V>,
189    ) -> triblesetrangeconstraint::TribleSetRangeConstraint {
190        triblesetrangeconstraint::TribleSetRangeConstraint::new(variable, min, max, self.clone())
191    }
192
193    /// Iterates over all tribles in EAV order.
194    pub fn iter(&self) -> TribleSetIterator<'_> {
195        TribleSetIterator {
196            inner: self
197                .eav
198                .iter()
199                .map(|data| Trible::as_transmute_raw_unchecked(data)),
200        }
201    }
202}
203
204impl PartialEq for TribleSet {
205    fn eq(&self, other: &Self) -> bool {
206        self.eav == other.eav
207    }
208}
209
210impl Eq for TribleSet {}
211
212impl Default for TribleSetFingerprint {
213    fn default() -> Self {
214        Self::EMPTY
215    }
216}
217
218impl From<&TribleSet> for TribleSetFingerprint {
219    fn from(set: &TribleSet) -> Self {
220        set.fingerprint()
221    }
222}
223
224impl AddAssign for TribleSet {
225    fn add_assign(&mut self, rhs: Self) {
226        self.union(rhs);
227    }
228}
229
230impl Add for TribleSet {
231    type Output = Self;
232
233    fn add(mut self, rhs: Self) -> Self::Output {
234        self.union(rhs);
235        self
236    }
237}
238
239impl FromIterator<Trible> for TribleSet {
240    fn from_iter<I: IntoIterator<Item = Trible>>(iter: I) -> Self {
241        let mut set = TribleSet::new();
242
243        for t in iter {
244            set.insert(&t);
245        }
246
247        set
248    }
249}
250
251impl TriblePattern for TribleSet {
252    type PatternConstraint<'a> = TribleSetConstraint;
253
254    fn pattern<V: ValueSchema>(
255        &self,
256        e: Variable<GenId>,
257        a: Variable<GenId>,
258        v: Variable<V>,
259    ) -> Self::PatternConstraint<'static> {
260        TribleSetConstraint::new(e, a, v, self.clone())
261    }
262}
263
264impl<'a> Iterator for TribleSetIterator<'a> {
265    type Item = &'a Trible;
266
267    fn next(&mut self) -> Option<Self::Item> {
268        self.inner.next()
269    }
270}
271
272impl<'a> IntoIterator for &'a TribleSet {
273    type Item = &'a Trible;
274    type IntoIter = TribleSetIterator<'a>;
275
276    fn into_iter(self) -> Self::IntoIter {
277        self.iter()
278    }
279}
280
281impl Default for TribleSet {
282    fn default() -> Self {
283        Self::new()
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use crate::examples::literature;
290    use crate::prelude::*;
291
292    use super::*;
293    use fake::faker::lorem::en::Words;
294    use fake::faker::name::raw::FirstName;
295    use fake::faker::name::raw::LastName;
296    use fake::locales::EN;
297    use fake::Fake;
298
299    use rayon::iter::IntoParallelIterator;
300    use rayon::iter::ParallelIterator;
301
302    #[test]
303    fn union() {
304        let mut kb = TribleSet::new();
305        for _i in 0..100 {
306            let author = ufoid();
307            let book = ufoid();
308            kb += entity! { &author @
309               literature::firstname: FirstName(EN).fake::<String>(),
310               literature::lastname: LastName(EN).fake::<String>(),
311            };
312            kb += entity! { &book @
313               literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
314               literature::author: &author
315            };
316        }
317        assert_eq!(kb.len(), 400);
318    }
319
320    #[test]
321    fn union_parallel() {
322        let kb = (0..1000)
323            .into_par_iter()
324            .flat_map(|_| {
325                let author = ufoid();
326                let book = ufoid();
327                [
328                    entity! { &author @
329                       literature::firstname: FirstName(EN).fake::<String>(),
330                       literature::lastname: LastName(EN).fake::<String>(),
331                    },
332                    entity! { &book @
333                       literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
334                       literature::author: &author
335                    },
336                ]
337            })
338            .reduce(Fragment::default, |a, b| a + b);
339        assert_eq!(kb.len(), 4000);
340    }
341
342    #[test]
343    fn intersection() {
344        let mut kb1 = TribleSet::new();
345        let mut kb2 = TribleSet::new();
346        for _i in 0..100 {
347            let author = ufoid();
348            let book = ufoid();
349            kb1 += entity! { &author @
350               literature::firstname: FirstName(EN).fake::<String>(),
351               literature::lastname: LastName(EN).fake::<String>(),
352            };
353            kb1 += entity! { &book @
354               literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
355               literature::author: &author
356            };
357            kb2 += entity! { &author @
358               literature::firstname: FirstName(EN).fake::<String>(),
359               literature::lastname: LastName(EN).fake::<String>(),
360            };
361            kb2 += entity! { &book @
362               literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
363               literature::author: &author
364            };
365        }
366        let intersection = kb1.intersect(&kb2);
367        // Verify that the intersection contains only elements present in both kb1 and kb2
368        for trible in &intersection {
369            assert!(kb1.contains(trible));
370            assert!(kb2.contains(trible));
371        }
372    }
373
374    #[test]
375    fn difference() {
376        let mut kb1 = TribleSet::new();
377        let mut kb2 = TribleSet::new();
378        for _i in 0..100 {
379            let author = ufoid();
380            let book = ufoid();
381            kb1 += entity! { &author @
382               literature::firstname: FirstName(EN).fake::<String>(),
383               literature::lastname: LastName(EN).fake::<String>(),
384            };
385            kb1 += entity! { &book @
386               literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
387               literature::author: &author
388            };
389            if _i % 2 == 0 {
390                kb2 += entity! { &author @
391                   literature::firstname: FirstName(EN).fake::<String>(),
392                   literature::lastname: LastName(EN).fake::<String>(),
393                };
394                kb2 += entity! { &book @
395                   literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
396                   literature::author: &author
397                };
398            }
399        }
400        let difference = kb1.difference(&kb2);
401        // Verify that the difference contains only elements present in kb1 but not in kb2
402        for trible in &difference {
403            assert!(kb1.contains(trible));
404            assert!(!kb2.contains(trible));
405        }
406    }
407
408    #[test]
409    fn test_contains() {
410        let mut kb = TribleSet::new();
411        let author = ufoid();
412        let book = ufoid();
413        let author_tribles = entity! { &author @
414           literature::firstname: FirstName(EN).fake::<String>(),
415           literature::lastname: LastName(EN).fake::<String>(),
416        };
417        let book_tribles = entity! { &book @
418           literature::title: Words(1..3).fake::<Vec<String>>().join(" "),
419           literature::author: &author
420        };
421
422        kb += author_tribles.clone();
423        kb += book_tribles.clone();
424
425        for trible in &author_tribles {
426            assert!(kb.contains(trible));
427        }
428        for trible in &book_tribles {
429            assert!(kb.contains(trible));
430        }
431
432        let non_existent_trible = entity! { &ufoid() @
433           literature::firstname: FirstName(EN).fake::<String>(),
434           literature::lastname: LastName(EN).fake::<String>(),
435        };
436
437        for trible in &non_existent_trible {
438            assert!(!kb.contains(trible));
439        }
440    }
441}