sophia_api/
quad.rs

1//! A quad expresses a single fact within a context.
2//! Quads are like RDF [`triples`](crate::triple)
3//! augmented with an optional graph name.
4//!
5//! They are the individual statements of an RDF [datasets](crate::dataset).
6use crate::term::matcher::{GraphNameMatcher, TermMatcher};
7use crate::term::{graph_name_eq, GraphName, Term};
8
9/// Type alias for terms borrowed from a quad.
10pub type QBorrowTerm<'a, T> = <<T as Quad>::Term as Term>::BorrowTerm<'a>;
11/// The typical structure representing a Quad of terms T
12pub type Spog<T> = ([T; 3], GraphName<T>);
13/// An alternative structure representing a Quad of terms T
14/// (useful when sorting first by graph is required)
15pub type Gspo<T> = (GraphName<T>, [T; 3]);
16
17/// This trait represents an abstract RDF quad,
18/// and provide convenient methods for working with quads.
19pub trait Quad {
20    /// The type of [`Term`] used by this quad when borrowing it
21    type Term: Term;
22
23    /// The subject of this quad.
24    fn s(&self) -> QBorrowTerm<Self>;
25
26    /// The predicate of this quad.
27    fn p(&self) -> QBorrowTerm<Self>;
28
29    /// The object of this quad.
30    fn o(&self) -> QBorrowTerm<Self>;
31
32    /// The graph name of this quad.
33    ///
34    /// `None` means that this quad belongs to the default graph of the dataset.
35    fn g(&self) -> GraphName<QBorrowTerm<Self>>;
36
37    /// The four components of this quad, as a quad of borrowed terms.
38    ///
39    /// See also [`Quad::to_spog`].
40    #[inline]
41    fn spog(&self) -> Spog<QBorrowTerm<Self>> {
42        ([self.s(), self.p(), self.o()], self.g())
43    }
44
45    /// Consume this quad, returning its subject.
46    fn to_s(self) -> Self::Term
47    where
48        Self: Sized,
49    {
50        let [s, _, _] = self.to_spog().0;
51        s
52    }
53
54    /// Consume this quad, returning its predicate.
55    fn to_p(self) -> Self::Term
56    where
57        Self: Sized,
58    {
59        let [_, p, _] = self.to_spog().0;
60        p
61    }
62
63    /// Consume this quad, returning its object.
64    fn to_o(self) -> Self::Term
65    where
66        Self: Sized,
67    {
68        let [_, _, o] = self.to_spog().0;
69        o
70    }
71
72    /// Consume this quad, returning its graph name.
73    fn to_g(self) -> GraphName<Self::Term>
74    where
75        Self: Sized,
76    {
77        self.to_spog().1
78    }
79
80    /// Consume this quad, returning all its components.
81    ///
82    /// See also [`Quad::spog`].
83    fn to_spog(self) -> Spog<Self::Term>
84    where
85        Self: Sized;
86
87    /// Checks that the constituents terms of this quad match the respective matchers.
88    fn matched_by<S, P, O, G>(&self, sm: S, pm: P, om: O, gm: G) -> bool
89    where
90        S: TermMatcher,
91        P: TermMatcher,
92        O: TermMatcher,
93        G: GraphNameMatcher,
94    {
95        sm.matches(&self.s())
96            && pm.matches(&self.p())
97            && om.matches(&self.o())
98            && gm.matches(self.g().as_ref())
99    }
100
101    /// Check whether `other` is term-wise equal (using [`Term::eq`]) to `self`.
102    ///
103    /// See also [`eq_spog`](Quad::eq_spog), [`matched_by`](Quad::matched_by).
104    #[inline]
105    fn eq<T: Quad>(&self, other: T) -> bool {
106        self.eq_spog(other.s(), other.p(), other.o(), other.g())
107    }
108
109    /// Check whether the quad (`s`, `p`, `o`) is term-wise equal (using [`Term::eq`]) to `self`.
110    ///
111    /// See also [`eq`](Quad::eq), [`matched_by`](Quad::matched_by).
112    fn eq_spog<S: Term, P: Term, O: Term, G: Term>(
113        &self,
114        s: S,
115        p: P,
116        o: O,
117        g: GraphName<G>,
118    ) -> bool {
119        self.s().eq(s) && self.p().eq(p) && self.o().eq(o) && graph_name_eq(self.g(), g)
120    }
121
122    /// Convert this quad to a [`Triple`](crate::triple::Triple) (dropping the graph name)
123    ///
124    /// NB: if you do not wish to consume this quad,
125    /// you can combine this method with [`spog`](Quad::spog) as below:
126    /// ```
127    /// # use sophia_api::quad::Quad;
128    /// # use sophia_api::triple::Triple;
129    /// # fn test<T: Quad>(q: &T) -> impl Triple + '_ {
130    ///     q.spog().into_triple()   
131    /// # }
132    /// ```
133    fn into_triple(self) -> [Self::Term; 3]
134    where
135        Self: Sized,
136    {
137        self.to_spog().0
138    }
139}
140
141impl<T: Term> Quad for [T; 4] {
142    type Term = T;
143
144    fn s(&self) -> QBorrowTerm<Self> {
145        self[0].borrow_term()
146    }
147    fn p(&self) -> QBorrowTerm<Self> {
148        self[1].borrow_term()
149    }
150    fn o(&self) -> QBorrowTerm<Self> {
151        self[2].borrow_term()
152    }
153    fn g(&self) -> GraphName<QBorrowTerm<Self>> {
154        Some(self[3].borrow_term())
155    }
156    fn to_s(self) -> Self::Term {
157        let [s, _, _, _] = self;
158        s
159    }
160    fn to_p(self) -> Self::Term {
161        let [_, p, _, _] = self;
162        p
163    }
164    fn to_o(self) -> Self::Term {
165        let [_, _, o, _] = self;
166        o
167    }
168    fn to_g(self) -> GraphName<Self::Term> {
169        let [_, _, _, g] = self;
170        Some(g)
171    }
172    fn to_spog(self) -> Spog<Self::Term> {
173        let [s, p, o, g] = self;
174        ([s, p, o], Some(g))
175    }
176}
177
178// Spog<T>
179impl<T: Term> Quad for ([T; 3], GraphName<T>) {
180    type Term = T;
181
182    fn s(&self) -> QBorrowTerm<Self> {
183        self.0[0].borrow_term()
184    }
185    fn p(&self) -> QBorrowTerm<Self> {
186        self.0[1].borrow_term()
187    }
188    fn o(&self) -> QBorrowTerm<Self> {
189        self.0[2].borrow_term()
190    }
191    fn g(&self) -> GraphName<QBorrowTerm<Self>> {
192        self.1.as_ref().map(|gn| gn.borrow_term())
193    }
194    fn to_s(self) -> Self::Term {
195        let [s, _, _] = self.0;
196        s
197    }
198    fn to_p(self) -> Self::Term {
199        let [_, p, _] = self.0;
200        p
201    }
202    fn to_o(self) -> Self::Term {
203        let [_, _, o] = self.0;
204        o
205    }
206    fn to_g(self) -> GraphName<Self::Term> {
207        self.1
208    }
209    fn to_spog(self) -> Spog<Self::Term> {
210        self
211    }
212}
213
214// Gspo<T>
215impl<T: Term> Quad for (GraphName<T>, [T; 3]) {
216    type Term = T;
217
218    fn s(&self) -> QBorrowTerm<Self> {
219        self.1[0].borrow_term()
220    }
221    fn p(&self) -> QBorrowTerm<Self> {
222        self.1[1].borrow_term()
223    }
224    fn o(&self) -> QBorrowTerm<Self> {
225        self.1[2].borrow_term()
226    }
227    fn g(&self) -> GraphName<QBorrowTerm<'_, Self>> {
228        self.0.as_ref().map(|gn| gn.borrow_term())
229    }
230    fn to_s(self) -> Self::Term {
231        let [s, _, _] = self.1;
232        s
233    }
234    fn to_p(self) -> Self::Term {
235        let [_, p, _] = self.1;
236        p
237    }
238    fn to_o(self) -> Self::Term {
239        let [_, _, o] = self.1;
240        o
241    }
242    fn to_g(self) -> GraphName<Self::Term> {
243        self.0
244    }
245    fn to_spog(self) -> Spog<Self::Term> {
246        let (g, spo) = self;
247        (spo, g)
248    }
249}
250
251/// Iter over all the components of a [`Quad`]
252pub fn iter_spog<T: Quad>(q: T) -> impl Iterator<Item = T::Term> {
253    let (spo, g) = q.to_spog();
254    spo.into_iter().chain(g)
255}
256
257#[cfg(test)]
258mod check_implementability {
259    use super::*;
260    use crate::term::*;
261    use mownstr::MownStr;
262
263    #[derive(Clone, Copy, Debug)]
264    struct MyBnode(usize);
265
266    impl Term for MyBnode {
267        type BorrowTerm<'x> = Self;
268
269        fn kind(&self) -> TermKind {
270            TermKind::BlankNode
271        }
272        fn bnode_id(&self) -> Option<BnodeId<MownStr>> {
273            Some(BnodeId::new_unchecked(MownStr::from(format!(
274                "b{}",
275                self.0
276            ))))
277        }
278        fn borrow_term(&self) -> Self::BorrowTerm<'_> {
279            *self
280        }
281    }
282
283    #[derive(Clone, Copy, Debug)]
284    struct MyQuad([usize; 4]);
285
286    impl Quad for MyQuad {
287        type Term = MyBnode;
288
289        fn s(&self) -> QBorrowTerm<Self> {
290            MyBnode(self.0[0])
291        }
292        fn p(&self) -> QBorrowTerm<Self> {
293            MyBnode(self.0[1])
294        }
295        fn o(&self) -> QBorrowTerm<Self> {
296            MyBnode(self.0[2])
297        }
298        fn g(&self) -> GraphName<QBorrowTerm<Self>> {
299            match self.0[3] {
300                0 => None,
301                n => Some(MyBnode(n)),
302            }
303        }
304        fn to_s(self) -> Self::Term {
305            self.s()
306        }
307        fn to_p(self) -> Self::Term {
308            self.p()
309        }
310        fn to_o(self) -> Self::Term {
311            self.o()
312        }
313        fn to_g(self) -> GraphName<Self::Term> {
314            self.g()
315        }
316        fn to_spog(self) -> Spog<Self::Term> {
317            ([self.s(), self.p(), self.o()], self.g())
318        }
319    }
320
321    #[allow(dead_code)] // only checks that this compiles
322    fn check_quad_impl(t: [SimpleTerm; 4]) {
323        fn foo<T: Quad>(t: T) {
324            println!("{:?}", t.s().kind());
325        }
326        let rt = t.spog();
327        foo(rt);
328        {
329            let rt2 = t.spog();
330            foo(rt2);
331        }
332        foo(rt);
333        foo(rt.spog());
334        foo(t);
335
336        let mt = MyQuad([1, 2, 3, 0]);
337        let rmt = mt.spog();
338        foo(rmt);
339        {
340            let rmt2 = mt.spog();
341            foo(rmt2);
342        }
343        foo(rmt);
344        foo(rmt.spog());
345        foo(mt);
346    }
347}
348
349#[cfg(test)]
350mod test_quad {
351    use super::*;
352    use crate::term::SimpleTerm;
353    use sophia_iri::IriRef;
354
355    const S: IriRef<&str> = IriRef::new_unchecked_const("tag:s");
356    const P: IriRef<&str> = IriRef::new_unchecked_const("tag:o");
357    const O: IriRef<&str> = IriRef::new_unchecked_const("tag:p");
358    const G: GraphName<IriRef<&str>> = Some(IriRef::new_unchecked_const("tag:g"));
359
360    #[test]
361    fn quad_matched_by() {
362        use crate::term::matcher::Any;
363        let q = ([S, P, O], G);
364
365        assert!(q.matched_by(Any, Any, Any, Any));
366        assert!(q.matched_by([S], [P], [O], [G]));
367        assert!(q.matched_by([O, S], [S, P], [P, O], [None, G]));
368        let istag = |t: SimpleTerm| t.iri().map(|iri| iri.starts_with("tag:")).unwrap_or(false);
369        assert!(q.matched_by(istag, istag, istag, istag.gn()));
370
371        let none: Option<IriRef<&str>> = None;
372        assert!(!q.matched_by(none, Any, Any, Any));
373        assert!(!q.matched_by(Any, none, Any, Any));
374        assert!(!q.matched_by(Any, Any, none, Any));
375        assert!(!q.matched_by(Any, Any, Any, none.gn()));
376        assert!(!q.matched_by([P, O], Any, Any, Any));
377        assert!(!q.matched_by(Any, [S, O], Any, Any));
378        assert!(!q.matched_by(Any, Any, [S, P], Any));
379        assert!(!q.matched_by(Any, Any, Any, [S, P, O].gn()));
380        let notag = |t: SimpleTerm| t.iri().map(|iri| !iri.starts_with("tag:")).unwrap_or(true);
381        assert!(!q.matched_by(notag, Any, Any, Any));
382        assert!(!q.matched_by(Any, notag, Any, Any));
383        assert!(!q.matched_by(Any, Any, notag, Any));
384        assert!(!q.matched_by(Any, Any, Any, notag.gn()));
385
386        let ts = vec![
387            ([S, P, S], G),
388            ([S, P, P], G),
389            ([S, P, O], G),
390            ([P, P, S], G),
391            ([P, P, P], G),
392            ([P, P, O], G),
393            ([O, P, S], G),
394            ([O, P, P], G),
395            ([O, P, O], G),
396            ([S, P, S], None),
397            ([P, P, P], None),
398            ([O, P, O], None),
399        ];
400        let c = ts
401            .iter()
402            .filter(|t| t.matched_by([S, O], Any, [S, O], Any))
403            .count();
404        assert_eq!(c, 6);
405
406        let default = G.filter(|_| false);
407        let c = ts
408            .iter()
409            .filter(|t| t.matched_by([S], Any, Any, [default]))
410            .count();
411        assert_eq!(c, 1);
412    }
413}