Skip to main content

vertep_store/
reification.rs

1//! Annotations on statements, written as RDF 1.2 reifiers or, on stores that only speak
2//! RDF 1.1, as classic `rdf:Statement` reification, and read back as the same annotation.
3//!
4//! The encoding is selected by the graph store's declared [`RdfCapability`]:
5//!
6//! | Capability | Quads written for `s p o` annotated with `k v` |
7//! |---|---|
8//! | RDF 1.2 | `s p o`, `r rdf:reifies <<( s p o )>>`, `r k v` |
9//! | RDF 1.1 | `s p o`, `r rdf:type rdf:Statement`, `r rdf:subject s`, `r rdf:predicate p`, `r rdf:object o`, `r k v` |
10//!
11//! In both encodings the annotated statement itself is asserted, as RDF 1.2 annotation syntax
12//! (`s p o {| k v |}`) does.
13
14use std::collections::BTreeMap;
15
16use oxrdf::vocab::rdf;
17use oxrdf::{
18    BlankNode, Dataset, GraphName, GraphNameRef, NamedNode, NamedOrBlankNode, Quad, Term, TermRef,
19    Triple,
20};
21
22use crate::error::StoreError;
23use crate::graph::{GraphStore, RdfCapability};
24
25/// `rdf:reifies` (RDF 1.2).
26pub const RDF_REIFIES: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies";
27
28/// An annotation: properties about one asserted statement in one graph.
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct Annotation {
31    /// The graph holding the statement and its annotation.
32    pub graph: GraphName,
33    /// The annotated statement.
34    pub triple: Triple,
35    /// The resource standing for the statement (the reifier). A store may rename it: tiramemsu
36    /// resolves every reifier of a statement to the statement's own id.
37    pub reifier: NamedOrBlankNode,
38    /// The annotation's properties, such as a confidence or the journal entry that caused it.
39    pub properties: Vec<(NamedNode, Term)>,
40}
41
42impl Annotation {
43    /// An annotation of `triple` in `graph` with a fresh blank node reifier.
44    pub fn new(graph: impl Into<GraphName>, triple: Triple) -> Self {
45        Annotation {
46            graph: graph.into(),
47            triple,
48            reifier: BlankNode::default().into(),
49            properties: Vec::new(),
50        }
51    }
52
53    /// This annotation with reifier `r`.
54    pub fn with_reifier(mut self, r: impl Into<NamedOrBlankNode>) -> Self {
55        self.reifier = r.into();
56        self
57    }
58
59    /// This annotation with one more property.
60    pub fn with(mut self, key: NamedNode, value: impl Into<Term>) -> Self {
61        self.properties.push((key, value.into()));
62        self
63    }
64
65    /// True when both annotate the same statement in the same graph with the same set of
66    /// properties, whatever their reifiers.
67    pub fn same_content(&self, other: &Annotation) -> bool {
68        let mut a = self.properties.clone();
69        let mut b = other.properties.clone();
70        a.sort_by_key(|(k, v)| (k.to_string(), v.to_string()));
71        a.dedup();
72        b.sort_by_key(|(k, v)| (k.to_string(), v.to_string()));
73        b.dedup();
74        self.graph == other.graph && self.triple == other.triple && a == b
75    }
76}
77
78/// The quads encoding `annotation` for a store with capability `rdf`.
79// @tg: implements:: [[openspec:storage#RDF 1.1 Reification Fallback]]
80pub fn encode(annotation: &Annotation, rdf: RdfCapability) -> Vec<Quad> {
81    let g = annotation.graph.clone();
82    let r = annotation.reifier.clone();
83    let t = &annotation.triple;
84    let mut quads = vec![t.clone().in_graph(g.clone())];
85    match rdf {
86        RdfCapability::Rdf12 => quads.push(Quad::new(
87            r.clone(),
88            NamedNode::new_unchecked(RDF_REIFIES),
89            Term::Triple(Box::new(t.clone())),
90            g.clone(),
91        )),
92        RdfCapability::Rdf11 => {
93            quads.push(Quad::new(r.clone(), rdf::TYPE, rdf::STATEMENT, g.clone()));
94            quads.push(Quad::new(
95                r.clone(),
96                rdf::SUBJECT,
97                t.subject.clone(),
98                g.clone(),
99            ));
100            quads.push(Quad::new(
101                r.clone(),
102                rdf::PREDICATE,
103                t.predicate.clone(),
104                g.clone(),
105            ));
106            quads.push(Quad::new(
107                r.clone(),
108                rdf::OBJECT,
109                t.object.clone(),
110                g.clone(),
111            ));
112        }
113    }
114    for (k, v) in &annotation.properties {
115        quads.push(Quad::new(r.clone(), k.clone(), v.clone(), g.clone()));
116    }
117    quads
118}
119
120/// Writes `annotations` to `store` in the encoding its declared capability selects.
121///
122/// # Errors
123///
124/// The errors of [`GraphStore::insert_quads`].
125pub fn write_annotations(
126    store: &dyn GraphStore,
127    annotations: &[Annotation],
128) -> Result<(), StoreError> {
129    let rdf = store.rdf_capability();
130    let quads: Vec<Quad> = annotations.iter().flat_map(|a| encode(a, rdf)).collect();
131    store.insert_quads(&quads)
132}
133
134fn structural(rdf: RdfCapability, key: &NamedNode, value: &Term) -> bool {
135    match rdf {
136        RdfCapability::Rdf12 => key.as_str() == RDF_REIFIES,
137        RdfCapability::Rdf11 => {
138            key.as_ref() == rdf::SUBJECT
139                || key.as_ref() == rdf::PREDICATE
140                || key.as_ref() == rdf::OBJECT
141                || (key.as_ref() == rdf::TYPE && value.as_ref() == TermRef::from(rdf::STATEMENT))
142        }
143    }
144}
145
146/// Reads the annotations of `triple` in `graph` from `store`, in the encoding its declared
147/// capability selects. Annotations by different reifiers come back separately.
148///
149/// # Errors
150///
151/// The errors of [`GraphStore::query`].
152pub fn read_annotations(
153    store: &dyn GraphStore,
154    graph: &GraphName,
155    triple: &Triple,
156) -> Result<Vec<Annotation>, StoreError> {
157    let caps = store.capabilities();
158    let (s, p, o) = (&triple.subject, &triple.predicate, &triple.object);
159    let body = match caps.rdf {
160        RdfCapability::Rdf12 => format!("{s} {p} {o} ~ ?r {{| ?k ?v |}} ."),
161        RdfCapability::Rdf11 => format!(
162            "{s} {p} {o} . ?r a <{st}> ; <{rs}> {s} ; <{rp}> {p} ; <{ro}> {o} ; ?k ?v .",
163            st = rdf::STATEMENT.as_str(),
164            rs = rdf::SUBJECT.as_str(),
165            rp = rdf::PREDICATE.as_str(),
166            ro = rdf::OBJECT.as_str(),
167        ),
168    };
169    let query = match graph {
170        GraphName::NamedNode(g) => format!("SELECT ?r ?k ?v WHERE {{ GRAPH {g} {{ {body} }} }}"),
171        GraphName::DefaultGraph => format!("SELECT ?r ?k ?v WHERE {{ {body} }}"),
172        GraphName::BlankNode(_) => {
173            return Err(StoreError::invalid("annotations in a blank-node graph"));
174        }
175    };
176    let solutions = store.query(&query)?.into_solutions()?;
177    let mut by_reifier: BTreeMap<String, Annotation> = BTreeMap::new();
178    for i in 0..solutions.len() {
179        let (Some(r), Some(Term::NamedNode(k)), Some(v)) = (
180            solutions.get(i, "r"),
181            solutions.get(i, "k"),
182            solutions.get(i, "v"),
183        ) else {
184            continue;
185        };
186        let reifier = match r {
187            Term::NamedNode(n) => NamedOrBlankNode::from(n.clone()),
188            Term::BlankNode(b) => NamedOrBlankNode::from(b.clone()),
189            _ => continue,
190        };
191        let a = by_reifier
192            .entry(r.to_string())
193            .or_insert_with(|| Annotation {
194                graph: graph.clone(),
195                triple: triple.clone(),
196                reifier,
197                properties: Vec::new(),
198            });
199        if structural(caps.rdf, k, v) || caps.is_reserved(k.as_str()) {
200            continue;
201        }
202        let prop = (k.clone(), v.clone());
203        if !a.properties.contains(&prop) {
204            a.properties.push(prop);
205        }
206    }
207    Ok(by_reifier
208        .into_values()
209        .filter(|a| !a.properties.is_empty())
210        .collect())
211}
212
213/// Decodes every annotation in `dataset`, in both encodings: RDF 1.2 reifiers and RDF 1.1
214/// `rdf:Statement` resources. Only annotations of statements asserted in the same graph are
215/// returned.
216pub fn decode(dataset: &Dataset) -> Vec<Annotation> {
217    let mut out = Vec::new();
218    // RDF 1.2: r rdf:reifies <<( s p o )>>
219    let reifies = NamedNode::new_unchecked(RDF_REIFIES);
220    for q in dataset.quads_for_predicate(&reifies) {
221        let TermRef::Triple(t) = q.object else {
222            continue;
223        };
224        let triple = t.as_ref().into_owned();
225        let graph = q.graph_name.into_owned();
226        if !dataset.contains(&triple.clone().in_graph(graph.clone())) {
227            continue;
228        }
229        let reifier = q.subject.into_owned();
230        let properties = properties_of(dataset, &reifier, &graph, RdfCapability::Rdf12);
231        out.push(Annotation {
232            graph,
233            triple,
234            reifier,
235            properties,
236        });
237    }
238    // RDF 1.1: r a rdf:Statement ; rdf:subject s ; rdf:predicate p ; rdf:object o
239    for q in dataset.quads_for_object(rdf::STATEMENT) {
240        if q.predicate != rdf::TYPE {
241            continue;
242        }
243        let graph = q.graph_name.into_owned();
244        let reifier = q.subject.into_owned();
245        let one = |p: oxrdf::NamedNodeRef<'_>| -> Option<Term> {
246            dataset
247                .quads_for_subject(&reifier)
248                .find(|x| x.predicate == p && x.graph_name == GraphNameRef::from(&graph))
249                .map(|x| x.object.into_owned())
250        };
251        let (Some(s), Some(Term::NamedNode(p)), Some(o)) =
252            (one(rdf::SUBJECT), one(rdf::PREDICATE), one(rdf::OBJECT))
253        else {
254            continue;
255        };
256        let s = match s {
257            Term::NamedNode(n) => NamedOrBlankNode::from(n),
258            Term::BlankNode(b) => NamedOrBlankNode::from(b),
259            _ => continue,
260        };
261        let triple = Triple::new(s, p, o);
262        if !dataset.contains(&triple.clone().in_graph(graph.clone())) {
263            continue;
264        }
265        let properties = properties_of(dataset, &reifier, &graph, RdfCapability::Rdf11);
266        out.push(Annotation {
267            graph,
268            triple,
269            reifier,
270            properties,
271        });
272    }
273    out
274}
275
276fn properties_of(
277    dataset: &Dataset,
278    reifier: &NamedOrBlankNode,
279    graph: &GraphName,
280    rdf: RdfCapability,
281) -> Vec<(NamedNode, Term)> {
282    dataset
283        .quads_for_subject(reifier)
284        .filter(|q| q.graph_name == GraphNameRef::from(graph))
285        .map(|q| (q.predicate.into_owned(), q.object.into_owned()))
286        .filter(|(k, v)| !structural(rdf, k, v))
287        .collect()
288}