sophia_api 0.7.2

A Rust toolkit for RDF and Linked Data - Core API
Documentation
// this module is transparently re-exported by its parent `dataset`

use std::collections::HashSet;
use std::error::Error;
use std::hash::Hash;

use resiter::filter::*;
use resiter::map::*;

use crate::dataset::adapter::DatasetGraph;
use crate::quad::stream::*;
use crate::quad::streaming_mode::*;
use crate::quad::*;
use crate::term::matcher::*;
use crate::term::{same_graph_name, term_eq, TTerm, TermKind};
use crate::triple::stream::StreamResult;

use crate::graph::insert_if_absent;

/// Type alias for the terms returned by a dataset.
pub type DTerm<D> = <<<D as Dataset>::Quad as QuadStreamingMode>::UnsafeQuad as UnsafeQuad>::Term;
/// Type alias for the quads returned by a dataset.
pub type DQuad<'a, D> = StreamedQuad<'a, <D as Dataset>::Quad>;
/// Type alias for results iterators produced by a dataset.
pub type DResult<D, T> = Result<T, <D as Dataset>::Error>;
/// Type alias for fallible quad iterators produced by a dataset.
pub type DQuadSource<'a, D> = Box<dyn Iterator<Item = DResult<D, DQuad<'a, D>>> + 'a>;
/// Type alias for fallible hashsets of terms produced by a dataset.
///
/// See [`Dataset::quads`] for more information about how to use it.
pub type DResultTermSet<D> = DResult<D, HashSet<DTerm<D>>>;

/// Generic trait for RDF datasets.
///
/// For convenience, this trait is implemented
/// by [standard collections of quads](#foreign-impls).
///
/// NB: the semantics of this trait allows a dataset to contain duplicate quads;
/// see also [`SetDataset`](trait.SetDataset.html).
pub trait Dataset {
    /// Determine the type of [`Quad`]s
    /// that the methods of this dataset will yield
    /// (see [`streaming_mode`]).
    type Quad: QuadStreamingMode;
    /// The error type that this dataset may raise.
    type Error: 'static + Error;

    /// An iterator visiting all quads of this dataset in arbitrary order.
    ///
    /// This iterator is fallible:
    /// its items are `Result`s,
    /// an error may occur at any time during the iteration.
    ///
    /// # Examples
    ///
    /// The result of this method is an iterator,
    /// so it can be used in a `for` loop:
    /// ```
    /// # use sophia_api::dataset::Dataset;
    /// # use sophia_api::term::simple_iri::SimpleIri;
    /// # fn foo() -> Result<(), std::convert::Infallible> {
    /// # let dataset = Vec::<[SimpleIri;4]>::new();
    /// for q in dataset.quads() {
    ///     let q = q?; // rethrow error if any
    ///     // do something with q
    /// }
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// Another way is to use the specific methods provided by [`QuadSource`],
    /// for example:
    /// ```
    /// # use sophia_api::dataset::Dataset;
    /// # use sophia_api::quad::stream::QuadSource;
    /// # use sophia_api::term::simple_iri::SimpleIri;
    /// # fn foo() -> Result<(), std::convert::Infallible> {
    /// # let dataset = Vec::<[SimpleIri;4]>::new();
    /// dataset.quads().for_each_quad(|q| {
    ///     // do something with q
    /// })?; // rethrow error if any
    /// # Ok(())
    /// # }
    /// ```
    fn quads(&self) -> DQuadSource<Self>;

    /// An iterator visiting all quads with the given subject.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_s<'s, TS>(&'s self, s: &'s TS) -> DQuadSource<'s, Self>
    where
        TS: TTerm + ?Sized,
    {
        Box::new(self.quads().filter_ok(move |q| term_eq(q.s(), s)))
    }
    /// An iterator visiting all quads with the given predicate.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_p<'s, TP>(&'s self, p: &'s TP) -> DQuadSource<'s, Self>
    where
        TP: TTerm + ?Sized,
    {
        Box::new(self.quads().filter_ok(move |q| term_eq(q.p(), p)))
    }
    /// An iterator visiting add quads with the given object.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_o<'s, TS>(&'s self, o: &'s TS) -> DQuadSource<'s, Self>
    where
        TS: TTerm + ?Sized,
    {
        Box::new(self.quads().filter_ok(move |q| term_eq(q.o(), o)))
    }
    /// An iterator visiting add quads with the given graph name.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_g<'s, TS>(&'s self, g: Option<&'s TS>) -> DQuadSource<'s, Self>
    where
        TS: TTerm + ?Sized,
    {
        Box::new(self.quads().filter_ok(move |q| same_graph_name(q.g(), g)))
    }
    /// An iterator visiting add quads with the given subject and predicate.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_sp<'s, TS, TP>(&'s self, s: &'s TS, p: &'s TP) -> DQuadSource<'s, Self>
    where
        TS: TTerm + ?Sized,
        TP: TTerm + ?Sized,
    {
        Box::new(self.quads_with_s(s).filter_ok(move |q| term_eq(q.p(), p)))
    }
    /// An iterator visiting add quads with the given subject and object.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_so<'s, TS, TO>(&'s self, s: &'s TS, o: &'s TO) -> DQuadSource<'s, Self>
    where
        TS: TTerm + ?Sized,
        TO: TTerm + ?Sized,
    {
        Box::new(self.quads_with_s(s).filter_ok(move |q| term_eq(q.o(), o)))
    }
    /// An iterator visiting add quads with the given subject and graph name.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_sg<'s, TS, TG>(&'s self, s: &'s TS, g: Option<&'s TG>) -> DQuadSource<'s, Self>
    where
        TS: TTerm + ?Sized,
        TG: TTerm + ?Sized,
    {
        Box::new(self.quads_with_g(g).filter_ok(move |q| term_eq(q.s(), s)))
    }
    /// An iterator visiting add quads with the given predicate and object.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_po<'s, TP, TO>(&'s self, p: &'s TP, o: &'s TO) -> DQuadSource<'s, Self>
    where
        TP: TTerm + ?Sized,
        TO: TTerm + ?Sized,
    {
        Box::new(self.quads_with_p(p).filter_ok(move |q| term_eq(q.o(), o)))
    }
    /// An iterator visiting add quads with the given predicate and graph name.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_pg<'s, TP, TG>(&'s self, p: &'s TP, g: Option<&'s TG>) -> DQuadSource<'s, Self>
    where
        TP: TTerm + ?Sized,
        TG: TTerm + ?Sized,
    {
        Box::new(self.quads_with_g(g).filter_ok(move |q| term_eq(q.p(), p)))
    }
    /// An iterator visiting add quads with the given object and graph name.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_og<'s, TO, TG>(&'s self, o: &'s TO, g: Option<&'s TG>) -> DQuadSource<'s, Self>
    where
        TO: TTerm + ?Sized,
        TG: TTerm + ?Sized,
    {
        Box::new(self.quads_with_g(g).filter_ok(move |q| term_eq(q.o(), o)))
    }
    /// An iterator visiting add quads with the given subject, predicate and object.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_spo<'s, TS, TP, TO>(
        &'s self,
        s: &'s TS,
        p: &'s TP,
        o: &'s TO,
    ) -> DQuadSource<'s, Self>
    where
        TS: TTerm + ?Sized,
        TP: TTerm + ?Sized,
        TO: TTerm + ?Sized,
    {
        Box::new(
            self.quads_with_sp(s, p)
                .filter_ok(move |q| term_eq(q.o(), o)),
        )
    }
    /// An iterator visiting add quads with the given subject, predicate and graph name.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_spg<'s, TS, TP, TG>(
        &'s self,
        s: &'s TS,
        p: &'s TP,
        g: Option<&'s TG>,
    ) -> DQuadSource<'s, Self>
    where
        TS: TTerm + ?Sized,
        TP: TTerm + ?Sized,
        TG: TTerm + ?Sized,
    {
        Box::new(
            self.quads_with_sg(s, g)
                .filter_ok(move |q| term_eq(q.p(), p)),
        )
    }
    /// An iterator visiting add quads with the given subject, object and graph name.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_sog<'s, TS, TO, TG>(
        &'s self,
        s: &'s TS,
        o: &'s TO,
        g: Option<&'s TG>,
    ) -> DQuadSource<'s, Self>
    where
        TS: TTerm + ?Sized,
        TO: TTerm + ?Sized,
        TG: TTerm + ?Sized,
    {
        Box::new(
            self.quads_with_sg(s, g)
                .filter_ok(move |q| term_eq(q.o(), o)),
        )
    }
    /// An iterator visiting add quads with the given predicate, object and graph name.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_pog<'s, TP, TO, TG>(
        &'s self,
        p: &'s TP,
        o: &'s TO,
        g: Option<&'s TG>,
    ) -> DQuadSource<'s, Self>
    where
        TP: TTerm + ?Sized,
        TO: TTerm + ?Sized,
        TG: TTerm + ?Sized,
    {
        Box::new(
            self.quads_with_pg(p, g)
                .filter_ok(move |q| term_eq(q.o(), o)),
        )
    }
    /// An iterator visiting add quads with the given subject, predicate, object and graph name.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_with_spog<'s, TS, TP, TO, TG>(
        &'s self,
        s: &'s TS,
        p: &'s TP,
        o: &'s TO,
        g: Option<&'s TG>,
    ) -> DQuadSource<'s, Self>
    where
        TS: TTerm + ?Sized,
        TP: TTerm + ?Sized,
        TO: TTerm + ?Sized,
        TG: TTerm + ?Sized,
    {
        Box::new(
            self.quads_with_spg(s, p, g)
                .filter_ok(move |q| term_eq(q.o(), o)),
        )
    }

    /// Return `true` if this dataset contains the given quad.
    fn contains<'s, TS, TP, TO, TG>(
        &'s self,
        s: &'s TS,
        p: &'s TP,
        o: &'s TO,
        g: Option<&'s TG>,
    ) -> DResult<Self, bool>
    where
        TS: TTerm + ?Sized,
        TP: TTerm + ?Sized,
        TO: TTerm + ?Sized,
        TG: TTerm + ?Sized,
    {
        match self.quads_with_spog(s, p, o, g).next() {
            None => Ok(false),
            Some(Ok(_)) => Ok(true),
            Some(Err(err)) => Err(err),
        }
    }

    /// An iterator visiting add quads matching the given subject, predicate, object and graph name.
    ///
    /// See also [`quads`](#tymethod.quads).
    fn quads_matching<'s, S, P, O, G>(
        &'s self,
        ms: &'s S,
        mp: &'s P,
        mo: &'s O,
        mg: &'s G,
    ) -> DQuadSource<'s, Self>
    where
        S: TermMatcher + ?Sized,
        P: TermMatcher + ?Sized,
        O: TermMatcher + ?Sized,
        G: GraphNameMatcher + ?Sized,
    {
        match (ms.constant(), mp.constant(), mo.constant(), mg.constant()) {
            (None, None, None, None) => Box::from(self.quads().filter_ok(move |q| {
                ms.matches(q.s()) && mp.matches(q.p()) && mo.matches(q.o()) && mg.matches(q.g())
            })),
            (Some(s), None, None, None) => {
                Box::from(self.quads_with_s(s).filter_ok(move |q| {
                    mp.matches(q.p()) && mo.matches(q.o()) && mg.matches(q.g())
                }))
            }
            (None, Some(p), None, None) => {
                Box::from(self.quads_with_p(p).filter_ok(move |q| {
                    ms.matches(q.s()) && mo.matches(q.o()) && mg.matches(q.g())
                }))
            }
            (None, None, Some(o), None) => {
                Box::from(self.quads_with_o(o).filter_ok(move |q| {
                    ms.matches(q.s()) && mp.matches(q.p()) && mg.matches(q.g())
                }))
            }
            (None, None, None, Some(g)) => {
                Box::from(self.quads_with_g(g).filter_ok(move |q| {
                    ms.matches(q.s()) && mp.matches(q.p()) && mo.matches(q.o())
                }))
            }
            (Some(s), Some(p), None, None) => Box::from(
                self.quads_with_sp(s, p)
                    .filter_ok(move |q| mo.matches(q.o()) && mg.matches(q.g())),
            ),
            (Some(s), None, Some(o), None) => Box::from(
                self.quads_with_so(s, o)
                    .filter_ok(move |q| mp.matches(q.p()) && mg.matches(q.g())),
            ),
            (Some(s), None, None, Some(g)) => Box::from(
                self.quads_with_sg(s, g)
                    .filter_ok(move |q| mp.matches(q.p()) && mo.matches(q.o())),
            ),
            (None, Some(p), Some(o), None) => Box::from(
                self.quads_with_po(p, o)
                    .filter_ok(move |q| ms.matches(q.s()) && mg.matches(q.g())),
            ),
            (None, Some(p), None, Some(g)) => Box::from(
                self.quads_with_pg(p, g)
                    .filter_ok(move |q| ms.matches(q.s()) && mo.matches(q.o())),
            ),
            (None, None, Some(o), Some(g)) => Box::from(
                self.quads_with_og(o, g)
                    .filter_ok(move |q| ms.matches(q.s()) && mp.matches(q.p())),
            ),
            (Some(s), Some(p), Some(o), None) => Box::from(
                self.quads_with_spo(s, p, o)
                    .filter_ok(move |q| mg.matches(q.g())),
            ),
            (Some(s), Some(p), None, Some(g)) => Box::from(
                self.quads_with_spg(s, p, g)
                    .filter_ok(move |q| mo.matches(q.o())),
            ),
            (Some(s), None, Some(o), Some(g)) => Box::from(
                self.quads_with_sog(s, o, g)
                    .filter_ok(move |q| mp.matches(q.p())),
            ),
            (None, Some(p), Some(o), Some(g)) => Box::from(
                self.quads_with_pog(p, o, g)
                    .filter_ok(move |q| ms.matches(q.s())),
            ),
            (Some(s), Some(p), Some(o), Some(g)) => self.quads_with_spog(s, p, o, g),
        }
    }

    /// Build a Hashset of all the terms used as subject in this Dataset.
    fn subjects(&self) -> DResultTermSet<Self>
    where
        DTerm<Self>: Clone + Eq + Hash,
    {
        let mut res = std::collections::HashSet::new();
        for q in self.quads() {
            insert_if_absent(&mut res, q?.s());
        }
        Ok(res)
    }

    /// Build a Hashset of all the terms used as predicate in this Dataset.
    fn predicates(&self) -> DResultTermSet<Self>
    where
        DTerm<Self>: Clone + Eq + Hash,
    {
        let mut res = std::collections::HashSet::new();
        for q in self.quads() {
            insert_if_absent(&mut res, q?.p());
        }
        Ok(res)
    }

    /// Build a Hashset of all the terms used as object in this Dataset.
    fn objects(&self) -> DResultTermSet<Self>
    where
        DTerm<Self>: Clone + Eq + Hash,
    {
        let mut res = std::collections::HashSet::new();
        for q in self.quads() {
            insert_if_absent(&mut res, q?.o());
        }
        Ok(res)
    }

    /// Build a Hashset of all the terms used as graph names in this Dataset.
    fn graph_names(&self) -> DResultTermSet<Self>
    where
        DTerm<Self>: Clone + Eq + Hash,
    {
        let mut res = std::collections::HashSet::new();
        for q in self.quads() {
            let q = q?;
            let name = q.g();
            if let Some(name) = name {
                insert_if_absent(&mut res, name);
            }
        }
        Ok(res)
    }

    /// Build a Hashset of all the IRIs used in this Dataset.
    fn iris(&self) -> DResultTermSet<Self>
    where
        DTerm<Self>: Clone + Eq + Hash,
    {
        let mut res = std::collections::HashSet::new();
        for q in self.quads() {
            for i in q?.components() {
                if matches!(i.kind(), TermKind::Iri) {
                    insert_if_absent(&mut res, i)
                }
            }
        }
        Ok(res)
    }

    /// Build a Hashset of all the BNodes used in this Dataset.
    fn bnodes(&self) -> DResultTermSet<Self>
    where
        DTerm<Self>: Clone + Eq + Hash,
    {
        let mut res = std::collections::HashSet::new();
        for q in self.quads() {
            for i in q?.components() {
                if matches!(i.kind(), TermKind::BlankNode) {
                    insert_if_absent(&mut res, i)
                }
            }
        }
        Ok(res)
    }

    /// Build a Hashset of all the Literals used in this Dataset.
    fn literals(&self) -> DResultTermSet<Self>
    where
        DTerm<Self>: Clone + Eq + Hash,
    {
        let mut res = std::collections::HashSet::new();
        for q in self.quads() {
            for i in q?.components() {
                if matches!(i.kind(), TermKind::Literal) {
                    insert_if_absent(&mut res, i)
                }
            }
        }
        Ok(res)
    }

    /// Build a Hashset of all the variables used in this Dataset.
    fn variables(&self) -> DResultTermSet<Self>
    where
        DTerm<Self>: Clone + Eq + Hash,
    {
        let mut res = std::collections::HashSet::new();
        for q in self.quads() {
            for i in q?.components() {
                if matches!(i.kind(), TermKind::Variable) {
                    insert_if_absent(&mut res, i)
                }
            }
        }
        Ok(res)
    }

    /// Borrows one of the graphs of this dataset
    fn graph<'s, T>(
        &'s self,
        graph_name: Option<&'s T>,
    ) -> DatasetGraph<Self, &'s Self, Option<&'s T>>
    where
        T: TTerm + ?Sized,
    {
        DatasetGraph::new(self, graph_name)
    }

    /// Borrows mutably one of the graphs of this dataset
    fn graph_mut<'s, T>(
        &'s mut self,
        graph_name: Option<&'s T>,
    ) -> DatasetGraph<Self, &'s mut Self, Option<&'s T>>
    where
        T: TTerm + ?Sized,
    {
        DatasetGraph::new(self, graph_name)
    }

    fn union_graph<'s, T>(&'s self, gmatcher: T) -> DatasetGraph<Self, &'s Self, T>
    where
        T: GraphNameMatcher + 's,
    {
        DatasetGraph::new(self, gmatcher)
    }
}

/// A dataset that can be constructed from a [`QuadSource`]
pub trait CollectibleDataset: Dataset + Sized {
    fn from_quad_source<QS: QuadSource>(quad: QS) -> StreamResult<Self, QS::Error, Self::Error>;
}

/// Type alias for results produced by a mutable dataset.
pub type MdResult<D, T> = std::result::Result<T, <D as MutableDataset>::MutationError>;

#[allow(clippy::upper_case_acronyms)]
#[deprecated(
    since = "0.7.0",
    note = "Was renamed to MdResult, according to naming conventions"
)]
pub type MDResult<D, T> = MdResult<D, T>;

/// Generic trait for mutable RDF datasets.
///
/// NB: the semantics of this trait allows a dataset to contain duplicate quads;
/// see also [`SetDataset`].
///
pub trait MutableDataset: Dataset {
    /// The error type that this dataset may raise during mutations.
    type MutationError: 'static + Error;

    /// Insert the given quad in this dataset.
    ///
    /// # Return value
    /// The `bool` value returned in case of success is
    /// **not significant unless** this dataset also implements [`SetDataset`].
    ///
    /// If it does,
    /// `true` is returned iff the insertion actually changed the dataset.
    /// In other words,
    /// a return value of `false` means that the dataset was not changed,
    /// because the quad was already present in this [`SetDataset`].
    ///
    /// [`SetDataset`]: trait.SetDataset.html
    fn insert<TS, TP, TO, TG>(
        &mut self,
        s: &TS,
        p: &TP,
        o: &TO,
        g: Option<&TG>,
    ) -> MdResult<Self, bool>
    where
        TS: TTerm + ?Sized,
        TP: TTerm + ?Sized,
        TO: TTerm + ?Sized,
        TG: TTerm + ?Sized;

    /// Remove the given quad from this dataset.
    ///
    /// # Return value
    /// The `bool` value returned in case of success is
    /// **not significant unless** this dataset also implements [`SetDataset`].
    ///
    /// If it does,
    /// `true` is returned iff the removal actually changed the dataset.
    /// In other words,
    /// a return value of `false` means that the dataset was not changed,
    /// because the quad was already absent from this [`SetDataset`].
    ///
    /// [`SetDataset`]: trait.SetDataset.html
    fn remove<TS, TP, TO, TG>(
        &mut self,
        s: &TS,
        p: &TP,
        o: &TO,
        g: Option<&TG>,
    ) -> MdResult<Self, bool>
    where
        TS: TTerm + ?Sized,
        TP: TTerm + ?Sized,
        TO: TTerm + ?Sized,
        TG: TTerm + ?Sized;

    /// Insert into this dataset all quads from the given source.
    ///
    /// # Blank node scope
    /// The blank nodes contained in the quad source will be inserted as is.
    /// If they happen to have the same identifier as blank nodes already present,
    /// they will be considered equal.
    /// This might *not* be what you want,
    /// especially if the dataset contains data from a file,
    /// and you are inserting data from a different file.
    /// In that case, you should first transform the quad source,
    /// in order to get fresh blank node identifiers.
    ///
    /// # Return value
    /// The `usize` value returned in case of success is
    /// **not significant unless** this dataset also implements [`SetDataset`].
    ///
    /// If it does,
    /// the number of quads that were *actually* inserted
    /// (i.e. that were not already present in this [`SetDataset`])
    /// is returned.
    ///
    /// [`SetDataset`]: trait.SetDataset.html
    #[inline]
    fn insert_all<QS>(
        &mut self,
        src: QS,
    ) -> StreamResult<usize, QS::Error, <Self as MutableDataset>::MutationError>
    where
        QS: QuadSource,
    {
        let mut src = src;
        let mut c = 0;
        src.try_for_each_quad(|q| -> MdResult<Self, ()> {
            if self.insert(q.s(), q.p(), q.o(), q.g())? {
                c += 1;
            }
            Ok(())
        })
        .and(Ok(c))
    }

    /// Remove from this dataset all quads from the given source.
    ///
    /// # Return value
    /// The `usize` value returned in case of success is
    /// **not significant unless** this dataset also implements [`SetDataset`].
    ///
    /// If it does,
    /// the number of quads that were *actually* removed
    /// (i.e. that were not already absent from this [`SetDataset`])
    /// is returned.
    ///
    /// [`SetDataset`]: trait.SetDataset.html
    #[inline]
    fn remove_all<QS>(
        &mut self,
        src: QS,
    ) -> StreamResult<usize, QS::Error, <Self as MutableDataset>::MutationError>
    where
        QS: QuadSource,
    {
        let mut src = src;
        let mut c = 0;
        src.try_for_each_quad(|q| -> MdResult<Self, ()> {
            if self.remove(q.s(), q.p(), q.o(), q.g())? {
                c += 1;
            }
            Ok(())
        })
        .and(Ok(c))
    }

    /// Remove all quads matching the given matchers.
    ///
    /// # Return value
    /// The `usize` value returned in case of success is
    /// **not significant unless** this dataset also implements [`SetDataset`].
    ///
    /// If it does,
    /// the number of quads that were *actually* removed
    /// (i.e. that were not already absent from this [`SetDataset`])
    /// is returned.
    ///
    /// # Note to implementors
    /// The default implementation is rather naive,
    /// and could be improved in specific implementations of the trait.
    ///
    /// [`SetDataset`]: trait.SetDataset.html
    fn remove_matching<S, P, O, G>(
        &mut self,
        ms: &S,
        mp: &P,
        mo: &O,
        mg: &G,
    ) -> MdResult<Self, usize>
    where
        S: TermMatcher + ?Sized,
        P: TermMatcher + ?Sized,
        O: TermMatcher + ?Sized,
        G: GraphNameMatcher + ?Sized,
        DTerm<Self>: Clone,
        <Self as Dataset>::Error: Into<Self::MutationError>,
    {
        let to_remove = self
            .quads_matching(ms, mp, mo, mg)
            .map_ok(|q| {
                (
                    [q.s().clone(), q.p().clone(), q.o().clone()],
                    q.g().map(Clone::clone),
                )
            })
            .collect::<std::result::Result<Vec<_>, _>>()
            .map_err(Into::into)?;
        let mut to_remove = to_remove.into_iter().into_quad_source();
        self.remove_all(&mut to_remove)
            .map_err(|err| err.unwrap_sink_error())
    }

    #[allow(clippy::result_unit_err)] // workaround https://github.com/rust-lang/rust-clippy/issues/6546
    /// Keep only the quads matching the given matchers.
    ///
    /// # Note to implementors
    /// The default implementation is rather naive,
    /// and could be improved in specific implementations of the trait.
    fn retain_matching<S, P, O, G>(
        &mut self,
        ms: &S,
        mp: &P,
        mo: &O,
        mg: &G,
    ) -> Result<(), Self::MutationError>
    where
        S: TermMatcher + ?Sized,
        P: TermMatcher + ?Sized,
        O: TermMatcher + ?Sized,
        G: GraphNameMatcher + ?Sized,
        DTerm<Self>: Clone,
        <Self as Dataset>::Error: Into<Self::MutationError>,
    {
        let to_remove = self
            .quads()
            .filter_ok(|q| {
                !(ms.matches(q.s()) && mp.matches(q.p()) && mo.matches(q.o()) && mg.matches(q.g()))
            })
            .map_ok(|q| {
                (
                    [q.s().clone(), q.p().clone(), q.o().clone()],
                    q.g().map(Clone::clone),
                )
            })
            .collect::<std::result::Result<Vec<_>, _>>()
            .map_err(Into::into)?;
        let mut to_remove = to_remove.into_iter().into_quad_source();
        self.remove_all(&mut to_remove)
            .map_err(|err| err.unwrap_sink_error())?;
        Ok(())
    }
}

/// Marker trait constraining the semantics of
/// [`Dataset`] and [`MutableDataset`].
///
/// It guarantees that
/// (1) quads will never be returned / stored multiple times.
///
/// If the type also implements [`MutableDataset`],
/// it must also ensure that
/// (2) the `bool` or `usize` values returned by [`MutableDataset`]
/// methods accurately describe how many quads were actually added/removed.
///
/// # Note to implementors
/// A type implementing both [`Dataset`] and [`MutableDataset`],
/// enforcing (1) but failing to enforce (2)
/// *must not* implement this trait.
///
/// [`Dataset`]: trait.Dataset.html
/// [`MutableDataset`]: trait.MutableDataset.html

pub trait SetDataset: Dataset {}

#[cfg(test)]
mod test {
    // The code from this module is tested through its use in other modules
    // (especially the macro test_dataset_impl!).
}