1use 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
25pub const RDF_REIFIES: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies";
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct Annotation {
31 pub graph: GraphName,
33 pub triple: Triple,
35 pub reifier: NamedOrBlankNode,
38 pub properties: Vec<(NamedNode, Term)>,
40}
41
42impl Annotation {
43 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 pub fn with_reifier(mut self, r: impl Into<NamedOrBlankNode>) -> Self {
55 self.reifier = r.into();
56 self
57 }
58
59 pub fn with(mut self, key: NamedNode, value: impl Into<Term>) -> Self {
61 self.properties.push((key, value.into()));
62 self
63 }
64
65 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
78pub 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
120pub 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
146pub 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
213pub fn decode(dataset: &Dataset) -> Vec<Annotation> {
217 let mut out = Vec::new();
218 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 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}