1use std::collections::BTreeMap;
8use std::fmt::Write;
9
10use oxrdf::{GraphName, NamedNode, NamedOrBlankNode, Quad, Term};
11
12use crate::error::StoreError;
13use crate::graph::{QueryResults, Solutions};
14
15fn by_graph(quads: &[Quad]) -> BTreeMap<String, (GraphName, Vec<&Quad>)> {
17 let mut groups: BTreeMap<String, (GraphName, Vec<&Quad>)> = BTreeMap::new();
18 for q in quads {
19 groups
20 .entry(q.graph_name.to_string())
21 .or_insert_with(|| (q.graph_name.clone(), Vec::new()))
22 .1
23 .push(q);
24 }
25 groups
26}
27
28fn write_block(out: &mut String, quads: &[Quad]) {
29 for (_, (g, qs)) in by_graph(quads) {
30 let indent = match &g {
31 GraphName::DefaultGraph => " ",
32 _ => {
33 let _ = writeln!(out, " GRAPH {g} {{");
34 " "
35 }
36 };
37 for q in qs {
38 let _ = writeln!(out, "{indent}{} {} {} .", q.subject, q.predicate, q.object);
39 }
40 if !matches!(g, GraphName::DefaultGraph) {
41 out.push_str(" }\n");
42 }
43 }
44}
45
46pub fn insert_data(quads: &[Quad]) -> String {
48 let mut out = String::from("INSERT DATA {\n");
49 write_block(&mut out, quads);
50 out.push('}');
51 out
52}
53
54fn has_blank(q: &Quad) -> bool {
55 matches!(q.subject, NamedOrBlankNode::BlankNode(_))
56 || term_has_blank(&q.object)
57 || matches!(q.graph_name, GraphName::BlankNode(_))
58}
59
60fn term_has_blank(t: &Term) -> bool {
61 match t {
62 Term::BlankNode(_) => true,
63 Term::Triple(tr) => {
64 matches!(tr.subject, NamedOrBlankNode::BlankNode(_)) || term_has_blank(&tr.object)
65 }
66 _ => false,
67 }
68}
69
70pub fn delete_data(quads: &[Quad]) -> Result<String, StoreError> {
76 if quads.iter().any(has_blank) {
77 return Err(StoreError::invalid(
78 "blank nodes cannot be named for removal",
79 ));
80 }
81 let mut out = String::from("DELETE DATA {\n");
82 write_block(&mut out, quads);
83 out.push('}');
84 Ok(out)
85}
86
87pub fn clear_graph(graph: &NamedNode) -> String {
89 format!("CLEAR SILENT GRAPH {graph}")
90}
91
92pub fn collect_results(results: spareval::QueryResults<'_>) -> Result<QueryResults, StoreError> {
99 match results {
100 spareval::QueryResults::Boolean(b) => Ok(QueryResults::Boolean(b)),
101 spareval::QueryResults::Solutions(iter) => {
102 let variables = iter
103 .variables()
104 .iter()
105 .map(|v| v.as_str().to_owned())
106 .collect();
107 let mut rows = Vec::new();
108 for s in iter {
109 let s = s.map_err(StoreError::backend)?;
110 rows.push(s.values().to_vec());
111 }
112 Ok(QueryResults::Solutions(Solutions { variables, rows }))
113 }
114 spareval::QueryResults::Graph(iter) => {
115 let mut triples = Vec::new();
116 for t in iter {
117 triples.push(t.map_err(StoreError::backend)?);
118 }
119 Ok(QueryResults::Graph(triples))
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use oxrdf::{BlankNode, Literal, Triple};
128
129 fn n(s: &str) -> NamedNode {
130 NamedNode::new(format!("urn:t:{s}")).unwrap()
131 }
132
133 #[test]
134 fn insert_data_groups_by_graph() {
135 let quads = vec![
136 Quad::new(n("a"), n("p"), Literal::from("x\"y"), n("g")),
137 Quad::new(n("a"), n("p"), n("b"), GraphName::DefaultGraph),
138 Quad::new(
139 n("r"),
140 n("reifies"),
141 Term::Triple(Box::new(Triple::new(n("a"), n("p"), n("b")))),
142 n("g"),
143 ),
144 ];
145 let text = insert_data(&quads);
146 assert!(text.starts_with("INSERT DATA {"));
147 assert!(text.contains("GRAPH <urn:t:g> {"));
148 assert!(text.contains(r#"<urn:t:a> <urn:t:p> "x\"y" ."#));
149 assert!(text.contains("<<( <urn:t:a> <urn:t:p> <urn:t:b> )>>"));
150 spargebra::SparqlParser::new()
151 .parse_update(&text)
152 .expect("valid SPARQL 1.2 update");
153 }
154
155 #[test]
156 fn delete_data_refuses_blank_nodes() {
157 let q = Quad::new(BlankNode::default(), n("p"), n("o"), n("g"));
158 assert!(delete_data(&[q]).is_err());
159 let ok = delete_data(&[Quad::new(n("s"), n("p"), n("o"), n("g"))]).unwrap();
160 spargebra::SparqlParser::new().parse_update(&ok).unwrap();
161 spargebra::SparqlParser::new()
162 .parse_update(&clear_graph(&n("g")))
163 .unwrap();
164 }
165}