sophia_api/
dataset.rs

1//! An RDF dataset is composed of a default [dataset](crate::dataset),
2//! and zero or more named graphs, each associated with a dataset name.
3//!
4//! Another way to look at it is as a collection of [quad](crate::quad)s.
5//!
6//! This module provides [reusable abstractions](#traits)
7//! for different kinds of datasets,
8//! as well as a few implementations for them.
9
10use std::error::Error;
11
12use crate::graph::adapter::{DatasetGraph, PartialUnionGraph, UnionGraph};
13use crate::quad::{iter_spog, Quad};
14use crate::source::{IntoSource, QuadSource, StreamResult};
15use crate::term::matcher::{GraphNameMatcher, TermMatcher};
16use crate::term::{GraphName, SimpleTerm, Term};
17
18use resiter::{filter::*, filter_map::*, flat_map::*, map::*};
19
20mod _foreign_impl;
21pub mod adapter;
22#[cfg(any(test, feature = "test_macro"))]
23#[macro_use]
24pub mod test;
25
26/// Type alias for results produced by a dataset.
27pub type DResult<D, T> = Result<T, <D as Dataset>::Error>;
28/// Type alias for fallible quad iterators produced by a dataset.
29///
30/// See [`Dataset::quads`] for more information about how to use it.
31#[deprecated(
32    since = "0.8.1",
33    note = "prototypes of `quads` and `quads_matching` have changed"
34)]
35pub type DQuadSource<'a, D> = Box<dyn Iterator<Item = DResult<D, <D as Dataset>::Quad<'a>>> + 'a>;
36/// Type alias for terms produced by a dataset.
37pub type DTerm<'a, D> = <<D as Dataset>::Quad<'a> as Quad>::Term;
38/// Type alias for fallible term iterators produced by a dataset.
39///
40/// See [`Dataset::subjects`] for more information about how to use it.
41#[deprecated(
42    since = "0.8.1",
43    note = "prototypes of term-yielding methods have changed"
44)]
45pub type DTermSource<'a, D> = Box<dyn Iterator<Item = DResult<D, DTerm<'a, D>>> + 'a>;
46
47/// Generic trait for RDF datasets.
48///
49/// For convenience, this trait is implemented
50/// by [standard collections of quads](#foreign-impls).
51///
52/// NB: the semantics of this trait allows a dataset to contain duplicate quads;
53/// see also [`SetDataset`].
54pub trait Dataset {
55    /// Determine the type of [`Quad`]s
56    /// that the methods of this dataset will yield.
57    type Quad<'x>: Quad
58    where
59        Self: 'x;
60    /// The error type that this dataset may raise.
61    type Error: Error + Send + Sync + 'static;
62
63    /// An iterator visiting all quads of this dataset in arbitrary order.
64    ///
65    /// This iterator is fallible:
66    /// its items are `Result`s,
67    /// an error may occur at any time during the iteration.
68    ///
69    /// # Examples
70    ///
71    /// The result of this method is an iterator,
72    /// so it can be used in a `for` loop:
73    /// ```
74    /// # fn test() -> Result<(), Box<dyn std::error::Error>> {
75    /// # use sophia_api::dataset::Dataset;
76    /// # use sophia_api::term::SimpleTerm;
77    /// # let dataset = Vec::<[SimpleTerm;4]>::new();
78    /// #
79    /// for q in dataset.quads() {
80    ///     let q = q?; // rethrow error if any
81    ///     // do something with q
82    /// }
83    /// #
84    /// # Ok(())
85    /// # }
86    /// ```
87    /// Another way is to use the specific methods provided by [`QuadSource`],
88    /// for example:
89    /// ```
90    /// # use sophia_api::dataset::Dataset;
91    /// # use sophia_api::term::SimpleTerm;
92    /// # use sophia_api::source::QuadSource;
93    /// # fn test() -> Result<(), Box<dyn std::error::Error>> {
94    /// # let dataset = Vec::<[SimpleTerm;4]>::new();
95    /// #
96    /// dataset.quads().for_each_quad(|q| {
97    ///     // do something with q
98    /// })?; // rethrow error if any
99    /// #
100    /// # Ok(())
101    /// # }
102    /// ```
103    fn quads(&self) -> impl Iterator<Item = DResult<Self, Self::Quad<'_>>> + '_;
104
105    /// An iterator visiting all quads matching the given subject, predicate and object.
106    /// See [`crate::term::matcher`]
107    ///
108    /// See also [`quads`](Dataset::quads).
109    ///
110    /// # Usage
111    ///
112    /// Typical implementations of [`TermMatcher`] include arrays/slices of [`Term`]s,
113    /// closure accepting a [`SimpleTerm`], or the special matcher [`Any`].
114    ///
115    /// [`Term`]: crate::term::Term
116    /// [`SimpleTerm`]: crate::term::SimpleTerm
117    /// [`Any`]: crate::term::matcher::Any
118    /// ```
119    /// # use sophia_api::prelude::*;
120    /// # use sophia_api::ns::{Namespace, rdf};
121    /// #
122    /// # fn test<G: Dataset>(dataset: &G) -> Result<(), Box<dyn std::error::Error>>
123    /// # where
124    /// #     G: Dataset,
125    /// # {
126    /// #
127    /// let s = Namespace::new("http://schema.org/")?;
128    /// let city = s.get("City")?;
129    /// let country = s.get("Country")?;
130    ///
131    /// for q in dataset.quads_matching(Any, [&rdf::type_], [city, country], Any) {
132    ///     println!("{:?} was found", q?.s());
133    /// }
134    /// #
135    /// # Ok(()) }
136    /// ```
137    ///
138    /// Here is another example using a closure as a [`TermMatcher`].
139    ///
140    /// ```
141    /// # use sophia_api::dataset::Dataset;
142    /// # use sophia_api::term::{SimpleTerm, Term};
143    /// # use sophia_api::quad::Quad;
144    /// # use sophia_api::ns::rdfs;
145    /// #
146    /// # fn test<G>(dataset: &G) -> Result<(), Box<dyn std::error::Error>>
147    /// # where
148    /// #     G: Dataset,
149    /// # {
150    /// #
151    /// use sophia_api::term::matcher::Any;
152    ///
153    /// for q in dataset.quads_matching(
154    ///     Any,
155    ///     [&rdfs::label],
156    ///     |t: SimpleTerm| t.lexical_form().map(|v| v.contains("needle")).unwrap_or(false),
157    ///     Any,
158    /// ) {
159    ///     println!("{:?} was found", q?.s());
160    /// }
161    /// #
162    /// # Ok(()) }
163    /// ```
164    fn quads_matching<'s, S, P, O, G>(
165        &'s self,
166        sm: S,
167        pm: P,
168        om: O,
169        gm: G,
170    ) -> impl Iterator<Item = DResult<Self, Self::Quad<'_>>> + '_
171    where
172        S: TermMatcher + 's,
173        P: TermMatcher + 's,
174        O: TermMatcher + 's,
175        G: GraphNameMatcher + 's,
176    {
177        self.quads().filter_ok(move |q| {
178            q.matched_by(
179                sm.matcher_ref(),
180                pm.matcher_ref(),
181                om.matcher_ref(),
182                gm.matcher_ref(),
183            )
184        })
185    }
186
187    /// Return `true` if this dataset contains the given quad.
188    fn contains<TS, TP, TO, TG>(&self, s: TS, p: TP, o: TO, g: GraphName<TG>) -> DResult<Self, bool>
189    where
190        TS: Term,
191        TP: Term,
192        TO: Term,
193        TG: Term,
194    {
195        self.quads_matching([s], [p], [o], [g])
196            .next()
197            .transpose()
198            .map(|o| o.is_some())
199    }
200
201    /// Build a fallible iterator of all the terms used as subject in this Dataset.
202    ///
203    /// NB: implementations SHOULD avoid yielding the same term multiple times, but MAY do so.
204    /// Users MUST therefore be prepared to deal with duplicates.
205    fn subjects(&self) -> impl Iterator<Item = DResult<Self, DTerm<'_, Self>>> + '_ {
206        self.quads().map_ok(Quad::to_s)
207    }
208
209    /// Build a fallible iterator of all the terms used as predicate in this Dataset.
210    ///
211    /// NB: implementations SHOULD avoid yielding the same term multiple times, but MAY do so.
212    /// Users MUST therefore be prepared to deal with duplicates.
213    fn predicates(&self) -> impl Iterator<Item = DResult<Self, DTerm<'_, Self>>> + '_ {
214        self.quads().map_ok(Quad::to_p)
215    }
216
217    /// Build a fallible iterator of all the terms used as object in this Dataset.
218    ///
219    /// NB: implementations SHOULD avoid yielding the same term multiple times, but MAY do so.
220    /// Users MUST therefore be prepared to deal with duplicates.
221    fn objects(&self) -> impl Iterator<Item = DResult<Self, DTerm<'_, Self>>> + '_ {
222        self.quads().map_ok(Quad::to_o)
223    }
224
225    /// Build a fallible iterator of all the terms used as graph name in this Dataset.
226    ///
227    /// NB: implementations SHOULD avoid yielding the same term multiple times, but MAY do so.
228    /// Users MUST therefore be prepared to deal with duplicates.
229    fn graph_names(&self) -> impl Iterator<Item = DResult<Self, DTerm<'_, Self>>> + '_ {
230        self.quads().filter_map_ok(Quad::to_g)
231    }
232
233    /// Build a fallible iterator of all the IRIs used in this Dataset
234    /// (including those used inside quoted quads, if any).
235    ///
236    /// NB: implementations SHOULD avoid yielding the same term multiple times, but MAY do so.
237    /// Users MUST therefore be prepared to deal with duplicates.
238    fn iris(&self) -> impl Iterator<Item = DResult<Self, DTerm<'_, Self>>> + '_ {
239        self.quads()
240            .flat_map_ok(iter_spog)
241            .flat_map_ok(Term::to_atoms)
242            .filter_ok(Term::is_iri)
243    }
244
245    /// Build a fallible iterator of all the blank nodes used in this Dataset
246    /// (including those used inside quoted quads, if any).
247    ///
248    /// NB: implementations SHOULD avoid yielding the same term multiple times, but MAY do so.
249    /// Users MUST therefore be prepared to deal with duplicates.
250    fn blank_nodes(&self) -> impl Iterator<Item = DResult<Self, DTerm<'_, Self>>> + '_ {
251        self.quads()
252            .flat_map_ok(iter_spog)
253            .flat_map_ok(Term::to_atoms)
254            .filter_ok(Term::is_blank_node)
255    }
256
257    /// Build a fallible iterator of all the literals used in this Dataset
258    /// (including those used inside quoted quads, if any).
259    ///
260    /// NB: implementations SHOULD avoid yielding the same term multiple times, but MAY do so.
261    /// Users MUST therefore be prepared to deal with duplicates.
262    fn literals(&self) -> impl Iterator<Item = DResult<Self, DTerm<'_, Self>>> + '_ {
263        self.quads()
264            .flat_map_ok(iter_spog)
265            .flat_map_ok(Term::to_atoms)
266            .filter_ok(Term::is_literal)
267    }
268
269    /// Build a fallible iterator of all the quoted triples used in this Dataset
270    /// (including those used inside quoted triples, if any).
271    ///
272    /// NB: implementations SHOULD avoid yielding the same term multiple times, but MAY do so.
273    /// Users MUST therefore be prepared to deal with duplicates.
274    fn quoted_triples<'s>(&'s self) -> Box<dyn Iterator<Item = DResult<Self, DTerm<'_, Self>>> + '_>
275    where
276        DTerm<'s, Self>: Clone,
277    {
278        Box::new(
279            self.quads()
280                .flat_map_ok(iter_spog)
281                .flat_map_ok(Term::to_constituents)
282                .filter_ok(Term::is_triple),
283        )
284    }
285
286    /// Build a fallible iterator of all the variables used in this Dataset
287    /// (including those used inside quoted quads, if any).
288    ///
289    /// NB: implementations SHOULD avoid yielding the same term multiple times, but MAY do so.
290    /// Users MUST therefore be prepared to deal with duplicates.
291    fn variables(&self) -> impl Iterator<Item = DResult<Self, DTerm<'_, Self>>> + '_ {
292        self.quads()
293            .flat_map_ok(iter_spog)
294            .flat_map_ok(Term::to_atoms)
295            .filter_ok(Term::is_variable)
296    }
297
298    /// Borrows one of the graphs of this dataset
299    fn graph<T>(&self, graph_name: GraphName<T>) -> DatasetGraph<&Self, T>
300    where
301        T: for<'x> Term<BorrowTerm<'x> = DTerm<'x, Self>> + 'static,
302    {
303        DatasetGraph::new(self, graph_name)
304    }
305
306    /// Borrows mutably one of the graphs of this dataset
307    fn graph_mut<T>(&mut self, graph_name: GraphName<T>) -> DatasetGraph<&mut Self, T>
308    where
309        T: for<'x> Term<BorrowTerm<'x> = DTerm<'x, Self>> + 'static,
310    {
311        DatasetGraph::new(self, graph_name)
312    }
313
314    /// Borrows a graph that is the union of some of this dataset's graphs
315    fn partial_union_graph<M>(&self, selector: M) -> PartialUnionGraph<&Self, M>
316    where
317        M: GraphNameMatcher + Copy,
318    {
319        PartialUnionGraph::new(self, selector)
320    }
321
322    /// Borrows a graph that is the union of all this dataset's graphs (default and named)
323    fn union_graph(&self) -> UnionGraph<&Self> {
324        UnionGraph::new(self)
325    }
326
327    /// Convert into a graph that is the union of all this dataset's graphs (default and named)
328    fn into_union_graph(self) -> UnionGraph<Self>
329    where
330        Self: Sized,
331    {
332        UnionGraph::new(self)
333    }
334}
335
336/// A [`Dataset`] that can be constructed from a [`QuadSource`]
337pub trait CollectibleDataset: Dataset + Sized {
338    /// Construct a dataset from the given source
339    fn from_quad_source<TS: QuadSource>(quads: TS) -> StreamResult<Self, TS::Error, Self::Error>;
340}
341
342/// Type alias for results produced by a mutable dataset.
343pub type MdResult<D, T> = std::result::Result<T, <D as MutableDataset>::MutationError>;
344
345/// Generic trait for mutable RDF datasets.
346///
347/// NB: the semantics of this trait allows a dataset to contain duplicate quads;
348/// see also [`SetDataset`].
349pub trait MutableDataset: Dataset {
350    /// The error type that this dataset may raise during mutations.
351    type MutationError: Error + Send + Sync + 'static;
352
353    /// Insert the given quad in this dataset.
354    ///
355    /// # Return value
356    /// The `bool` value returned in case of success is
357    /// **not significant unless** this dataset also implements [`SetDataset`].
358    ///
359    /// If it does,
360    /// `true` is returned iff the insertion actually changed the dataset.
361    /// In other words,
362    /// a return value of `false` means that the dataset was not changed,
363    /// because the quad was already present in this [`SetDataset`].
364    ///
365    /// See also [`MutableDataset::insert_quad`]
366    ///
367    /// # Usage
368    /// ```
369    /// # use sophia_api::dataset::{MutableDataset, MdResult};
370    /// # use sophia_api::ns::{Namespace, rdf, rdfs, xsd};
371    /// # use sophia_api::term::SimpleTerm;
372    /// # fn populate<D: MutableDataset>(dataset: &mut D) -> MdResult<D, ()> {
373    /// #
374    /// let schema = Namespace::new("http://schema.org/").unwrap();
375    /// let s_name = schema.get("name").unwrap();
376    /// let default_graph: Option<&'static SimpleTerm<'static>> = None;
377    ///
378    /// dataset.insert(&s_name, &rdf::type_, &rdf::Property, default_graph)?;
379    /// dataset.insert(&s_name, &rdfs::range, &xsd::string, default_graph)?;
380    /// dataset.insert(&s_name, &rdfs::comment, "The name of the item.", Some(&rdfs::comment))?;
381    /// #
382    /// # Ok(())
383    /// # }
384    /// ```
385    fn insert<TS, TP, TO, TG>(
386        &mut self,
387        s: TS,
388        p: TP,
389        o: TO,
390        g: GraphName<TG>,
391    ) -> MdResult<Self, bool>
392    where
393        TS: Term,
394        TP: Term,
395        TO: Term,
396        TG: Term;
397
398    /// Insert in this graph the given quad.
399    ///
400    /// NB: if you want to insert a quad `q` while keeping its ownership,
401    /// you can still pass [`q.spog()`](Quad::spog).
402    ///
403    /// See also [`MutableDataset::insert`]
404    fn insert_quad<T>(&mut self, quad: T) -> MdResult<Self, bool>
405    where
406        T: Quad,
407    {
408        let ([s, p, o], g) = quad.to_spog();
409        self.insert(s, p, o, g)
410    }
411
412    /// Remove the given quad from this dataset.
413    ///
414    /// # Return value
415    /// The `bool` value returned in case of success is
416    /// **not significant unless** this dataset also implements [`SetDataset`].
417    ///
418    /// If it does,
419    /// `true` is returned iff the removal actually changed the dataset.
420    /// In other words,
421    /// a return value of `false` means that the dataset was not changed,
422    /// because the quad was already absent from this [`SetDataset`].
423    fn remove<TS, TP, TO, TG>(
424        &mut self,
425        s: TS,
426        p: TP,
427        o: TO,
428        g: GraphName<TG>,
429    ) -> MdResult<Self, bool>
430    where
431        TS: Term,
432        TP: Term,
433        TO: Term,
434        TG: Term;
435
436    /// Remove from this graph a the given quad.
437    ///
438    /// NB: if you want to remove a quad `q` while keeping its ownership,
439    /// you can still pass [`q.spog()`](Quad::spog).
440    ///
441    /// See also [MutableDataset::remove]
442    fn remove_quad<T>(&mut self, quad: T) -> MdResult<Self, bool>
443    where
444        T: Quad,
445    {
446        let ([s, p, o], g) = quad.to_spog();
447        self.remove(s, p, o, g)
448    }
449
450    /// Insert into this dataset all quads from the given source.
451    ///
452    /// # Blank node scope
453    /// The blank nodes contained in the quad source will be inserted as is.
454    /// If they happen to have the same identifier as blank nodes already present,
455    /// they will be considered equal.
456    /// This might *not* be what you want,
457    /// especially if the dataset contains data from a file,
458    /// and you are inserting data from a different file.
459    /// In that case, you should first transform the quad source,
460    /// in order to get fresh blank node identifiers.
461    ///
462    /// # Return value
463    /// The `usize` value returned in case of success is
464    /// **not significant unless** this dataset also implements [`SetDataset`].
465    ///
466    /// If it does,
467    /// the number of quads that were *actually* inserted
468    /// (i.e. that were not already present in this [`SetDataset`])
469    /// is returned.
470    #[inline]
471    fn insert_all<TS: QuadSource>(
472        &mut self,
473        src: TS,
474    ) -> StreamResult<usize, TS::Error, <Self as MutableDataset>::MutationError> {
475        let mut src = src;
476        let mut c = 0;
477        src.try_for_each_quad(|q| -> MdResult<Self, ()> {
478            if self.insert_quad(q.spog())? {
479                c += 1;
480            }
481            Ok(())
482        })
483        .and(Ok(c))
484    }
485
486    /// Remove from this dataset all quads from the given source.
487    ///
488    /// # Return value
489    /// The `usize` value returned in case of success is
490    /// **not significant unless** this dataset also implements [`SetDataset`].
491    ///
492    /// If it does,
493    /// the number of quads that were *actually* removed
494    /// (i.e. that were not already absent from this [`SetDataset`])
495    /// is returned.
496    #[inline]
497    fn remove_all<TS: QuadSource>(
498        &mut self,
499        src: TS,
500    ) -> StreamResult<usize, TS::Error, <Self as MutableDataset>::MutationError> {
501        let mut src = src;
502        let mut c = 0;
503        src.try_for_each_quad(|q| -> MdResult<Self, ()> {
504            if self.remove_quad(q.spog())? {
505                c += 1;
506            }
507            Ok(())
508        })
509        .and(Ok(c))
510    }
511
512    /// Remove all quads matching the given matchers.
513    ///
514    /// # Return value
515    /// The `usize` value returned in case of success is
516    /// **not significant unless** this dataset also implements [`SetDataset`].
517    ///
518    /// If it does,
519    /// the number of quads that were *actually* removed
520    /// (i.e. that were not already absent from this [`SetDataset`])
521    /// is returned.
522    ///
523    /// # Note to implementors
524    /// The default implementation is rather naive,
525    /// and could be improved in specific implementations of the trait.
526    fn remove_matching<S, P, O, G>(
527        &mut self,
528        ms: S,
529        mp: P,
530        mo: O,
531        mg: G,
532    ) -> Result<usize, Self::MutationError>
533    where
534        S: TermMatcher,
535        P: TermMatcher,
536        O: TermMatcher,
537        G: GraphNameMatcher,
538        Self::MutationError: From<Self::Error>,
539    {
540        let to_remove: Result<Vec<([SimpleTerm; 3], GraphName<SimpleTerm>)>, _> = self
541            .quads_matching(ms, mp, mo, mg)
542            .map_ok(|q| {
543                let (spo, g) = q.spog();
544                (spo.map(Term::into_term), g.map(Term::into_term))
545            })
546            .collect();
547        self.remove_all(to_remove?.into_iter().into_source())
548            .map_err(|err| err.unwrap_sink_error())
549    }
550
551    /// Keep only the quads matching the given matchers.
552    ///
553    /// # Note to implementors
554    /// The default implementation is rather naive,
555    /// and could be improved in specific implementations of the trait.
556    fn retain_matching<S, P, O, G>(
557        &mut self,
558        ms: S,
559        mp: P,
560        mo: O,
561        mg: G,
562    ) -> Result<(), Self::MutationError>
563    where
564        S: TermMatcher,
565        P: TermMatcher,
566        O: TermMatcher,
567        G: GraphNameMatcher,
568        Self::MutationError: From<Self::Error>,
569    {
570        let to_remove: Result<Vec<([SimpleTerm; 3], GraphName<SimpleTerm>)>, _> = self
571            .quads()
572            .filter_ok(|q| {
573                !q.matched_by(
574                    ms.matcher_ref(),
575                    mp.matcher_ref(),
576                    mo.matcher_ref(),
577                    mg.matcher_ref(),
578                )
579            })
580            .map_ok(|q| {
581                let (spo, g) = q.spog();
582                (spo.map(Term::into_term), g.map(Term::into_term))
583            })
584            .collect();
585        self.remove_all(to_remove?.into_iter().into_source())
586            .map_err(|err| err.unwrap_sink_error())?;
587        Ok(())
588    }
589}
590
591/// Marker trait constraining the semantics of
592/// [`Dataset`] and [`MutableDataset`].
593///
594/// It guarantees that
595/// (1) quads will never be returned / stored multiple times.
596///
597/// If the type also implements [`MutableDataset`],
598/// it must also ensure that
599/// (2) the `bool` or `usize` values returned by [`MutableDataset`]
600/// methods accurately describe how many quads were actually added/removed.
601///
602/// # Note to implementors
603/// A type implementing both [`Dataset`] and [`MutableDataset`],
604/// enforcing (1) but failing to enforce (2)
605/// *must not* implement this trait.
606pub trait SetDataset: Dataset {}
607
608mod check_implementability {
609    /// This is a naive implementation of an RDF-star dataset,
610    /// where the dataset maintains
611    /// - a list of terms (either atoms or index of quad)
612    /// - a list of triples (SPO indexes)
613    /// - a list of named graphs associated the triple indexes contained in the graph
614    ///
615    /// This avoids the need to store arbitrarily nested triples.
616    /// NB: unasserted triples are not used in any quoted graph.
617    use super::*;
618    use crate::term::SimpleTerm;
619    use std::collections::HashMap;
620
621    #[derive(Clone, Debug, Eq, PartialEq)]
622    #[allow(dead_code)] // testing implementability
623    enum MyInternalTerm {
624        Atom(SimpleTerm<'static>),
625        QuotedTriple(usize),
626    }
627    use MyInternalTerm::*;
628
629    #[derive(Clone, Debug)]
630    struct MyDataset {
631        terms: Vec<MyInternalTerm>,
632        triples: Vec<[usize; 3]>,
633        graphs: HashMap<usize, Vec<usize>>,
634    }
635
636    impl MyDataset {
637        fn make_term(&self, i: usize) -> SimpleTerm<'_> {
638            match &self.terms[i] {
639                Atom(t) => t.as_simple(),
640                QuotedTriple(j) => {
641                    SimpleTerm::Triple(Box::new(self.triples[*j].map(|k| self.make_term(k))))
642                }
643            }
644        }
645    }
646
647    impl Dataset for MyDataset {
648        type Quad<'x> = [SimpleTerm<'x>; 4] where Self: 'x;
649        type Error = std::convert::Infallible;
650
651        fn quads(&self) -> impl Iterator<Item = DResult<Self, Self::Quad<'_>>> + '_ {
652            self.graphs.iter().flat_map(move |(gi, tis)| {
653                let g = self.make_term(*gi);
654                tis.iter().copied().map(move |ti| {
655                    let [s, p, o] = self.triples[ti].map(|j| self.make_term(j));
656                    Ok([s, p, o, g.clone()])
657                })
658            })
659        }
660    }
661}
662
663#[cfg(test)]
664mod check_implementability_lazy_term {
665    /// This implementation is internally similar to the one above,
666    /// but using dedicated lazy implementations of Term
667    /// (lazy because it avoids allocating nested triples until forced)
668    use super::*;
669    use crate::term::{SimpleTerm, TermKind};
670    use std::collections::HashMap;
671
672    #[derive(Clone, Debug, Eq, PartialEq)]
673    #[allow(dead_code)] // testing implementability
674    enum MyInternalTerm {
675        Atom(SimpleTerm<'static>),
676        QuotedTriple(usize),
677    }
678    use MyInternalTerm::*;
679
680    #[derive(Clone, Debug)]
681    struct MyDataset {
682        terms: Vec<MyInternalTerm>,
683        triples: Vec<[usize; 3]>,
684        graphs: HashMap<usize, Vec<usize>>,
685    }
686
687    #[derive(Clone, Copy, Debug)]
688    struct MyTerm<'a> {
689        dataset: &'a MyDataset,
690        index: usize,
691    }
692
693    impl<'a> Term for MyTerm<'a> {
694        type BorrowTerm<'x> = MyTerm<'x> where Self: 'x;
695
696        fn kind(&self) -> crate::term::TermKind {
697            if let Atom(t) = &self.dataset.terms[self.index] {
698                t.kind()
699            } else {
700                TermKind::Triple
701            }
702        }
703
704        fn iri(&self) -> Option<crate::term::IriRef<mownstr::MownStr>> {
705            if let Atom(t) = &self.dataset.terms[self.index] {
706                t.iri()
707            } else {
708                None
709            }
710        }
711
712        fn bnode_id(&self) -> Option<crate::term::BnodeId<mownstr::MownStr>> {
713            if let Atom(t) = &self.dataset.terms[self.index] {
714                t.bnode_id()
715            } else {
716                None
717            }
718        }
719
720        fn lexical_form(&self) -> Option<mownstr::MownStr> {
721            if let Atom(t) = &self.dataset.terms[self.index] {
722                t.lexical_form()
723            } else {
724                None
725            }
726        }
727
728        fn datatype(&self) -> Option<crate::term::IriRef<mownstr::MownStr>> {
729            if let Atom(t) = &self.dataset.terms[self.index] {
730                t.datatype()
731            } else {
732                None
733            }
734        }
735
736        fn language_tag(&self) -> Option<crate::term::LanguageTag<mownstr::MownStr>> {
737            if let Atom(t) = &self.dataset.terms[self.index] {
738                t.language_tag()
739            } else {
740                None
741            }
742        }
743
744        fn variable(&self) -> Option<crate::term::VarName<mownstr::MownStr>> {
745            if let Atom(t) = &self.dataset.terms[self.index] {
746                t.variable()
747            } else {
748                None
749            }
750        }
751
752        fn triple(&self) -> Option<[Self::BorrowTerm<'_>; 3]> {
753            self.to_triple()
754        }
755
756        fn to_triple(self) -> Option<[Self; 3]> {
757            if let QuotedTriple(i) = &self.dataset.terms[self.index] {
758                Some(self.dataset.triples[*i].map(|t| MyTerm {
759                    dataset: self.dataset,
760                    index: t,
761                }))
762            } else {
763                None
764            }
765        }
766
767        fn borrow_term(&self) -> Self::BorrowTerm<'_> {
768            *self
769        }
770    }
771
772    impl Dataset for MyDataset {
773        type Quad<'x> = [MyTerm<'x>; 4] where Self: 'x;
774        type Error = std::convert::Infallible;
775
776        fn quads(&self) -> impl Iterator<Item = DResult<Self, Self::Quad<'_>>> + '_ {
777            self.graphs.iter().flat_map(move |(gi, tis)| {
778                let g = MyTerm {
779                    dataset: self,
780                    index: *gi,
781                };
782                tis.iter().copied().map(move |ti| {
783                    let [s, p, o] = self.triples[ti].map(|j| MyTerm {
784                        dataset: self,
785                        index: j,
786                    });
787                    Ok([s, p, o, g])
788                })
789            })
790        }
791    }
792}