oxigraph/sparql/
update.rs

1use crate::io::{RdfFormat, RdfParser};
2use crate::model::{GraphName as OxGraphName, GraphNameRef, Quad as OxQuad};
3use crate::sparql::algebra::QueryDataset;
4use crate::sparql::dataset::DatasetView;
5use crate::sparql::http::Client;
6use crate::sparql::{EvaluationError, Update, UpdateOptions};
7use crate::storage::StorageWriter;
8use oxiri::Iri;
9use oxrdfio::LoadedDocument;
10use rustc_hash::FxHashMap;
11use sparesults::QuerySolution;
12use spareval::{QueryEvaluator, QueryResults};
13use spargebra::algebra::{GraphPattern, GraphTarget};
14use spargebra::term::{
15    BlankNode, GraphName, GraphNamePattern, GroundQuad, GroundQuadPattern, GroundSubject,
16    GroundTerm, GroundTermPattern, GroundTriple, GroundTriplePattern, NamedNode, NamedNodePattern,
17    Quad, QuadPattern, Subject, Term, TermPattern, Triple, TriplePattern,
18};
19use spargebra::{GraphUpdateOperation, Query};
20use std::io;
21use std::io::Read;
22
23pub fn evaluate_update<'a, 'b: 'a>(
24    transaction: &'a mut StorageWriter<'b>,
25    update: &Update,
26    options: &UpdateOptions,
27) -> Result<(), EvaluationError> {
28    SimpleUpdateEvaluator {
29        transaction,
30        base_iri: update.inner.base_iri.clone(),
31        query_evaluator: options.query_options.clone().into_evaluator(),
32        client: Client::new(
33            options.query_options.http_timeout,
34            options.query_options.http_redirection_limit,
35        ),
36    }
37    .eval_all(&update.inner.operations, &update.using_datasets)
38}
39
40struct SimpleUpdateEvaluator<'a, 'b> {
41    transaction: &'a mut StorageWriter<'b>,
42    base_iri: Option<Iri<String>>,
43    query_evaluator: QueryEvaluator,
44    client: Client,
45}
46
47impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> {
48    fn eval_all(
49        &mut self,
50        updates: &[GraphUpdateOperation],
51        using_datasets: &[Option<QueryDataset>],
52    ) -> Result<(), EvaluationError> {
53        for (update, using_dataset) in updates.iter().zip(using_datasets) {
54            self.eval(update, using_dataset)?;
55        }
56        Ok(())
57    }
58
59    fn eval(
60        &mut self,
61        update: &GraphUpdateOperation,
62        using_dataset: &Option<QueryDataset>,
63    ) -> Result<(), EvaluationError> {
64        match update {
65            GraphUpdateOperation::InsertData { data } => self.eval_insert_data(data),
66            GraphUpdateOperation::DeleteData { data } => self.eval_delete_data(data),
67            GraphUpdateOperation::DeleteInsert {
68                delete,
69                insert,
70                pattern,
71                ..
72            } => self.eval_delete_insert(
73                delete,
74                insert,
75                using_dataset.as_ref().unwrap_or(&QueryDataset::new()),
76                pattern,
77            ),
78            GraphUpdateOperation::Load {
79                silent,
80                source,
81                destination,
82            } => {
83                if let Err(error) = self.eval_load(source, destination) {
84                    if *silent {
85                        Ok(())
86                    } else {
87                        Err(error)
88                    }
89                } else {
90                    Ok(())
91                }
92            }
93            GraphUpdateOperation::Clear { graph, silent } => self.eval_clear(graph, *silent),
94            GraphUpdateOperation::Create { graph, silent } => self.eval_create(graph, *silent),
95            GraphUpdateOperation::Drop { graph, silent } => self.eval_drop(graph, *silent),
96        }
97    }
98
99    fn eval_insert_data(&mut self, data: &[Quad]) -> Result<(), EvaluationError> {
100        let mut bnodes = FxHashMap::default();
101        for quad in data {
102            let quad = Self::convert_quad(quad, &mut bnodes);
103            self.transaction.insert(quad.as_ref())?;
104        }
105        Ok(())
106    }
107
108    fn eval_delete_data(&mut self, data: &[GroundQuad]) -> Result<(), EvaluationError> {
109        for quad in data {
110            let quad = Self::convert_ground_quad(quad);
111            self.transaction.remove(quad.as_ref())?;
112        }
113        Ok(())
114    }
115
116    fn eval_delete_insert(
117        &mut self,
118        delete: &[GroundQuadPattern],
119        insert: &[QuadPattern],
120        using: &QueryDataset,
121        algebra: &GraphPattern,
122    ) -> Result<(), EvaluationError> {
123        let QueryResults::Solutions(solutions) = self.query_evaluator.clone().execute(
124            DatasetView::new(self.transaction.reader(), using),
125            &Query::Select {
126                dataset: None,
127                pattern: algebra.clone(),
128                base_iri: self.base_iri.clone(),
129            },
130        )?
131        else {
132            unreachable!("We provided a SELECT query, we must get back solutions")
133        };
134
135        let mut bnodes = FxHashMap::default();
136        for solution in solutions {
137            let solution = solution?;
138            for quad in delete {
139                if let Some(quad) = Self::fill_ground_quad_pattern(quad, &solution) {
140                    self.transaction.remove(quad.as_ref())?;
141                }
142            }
143            for quad in insert {
144                if let Some(quad) = Self::fill_quad_pattern(quad, &solution, &mut bnodes) {
145                    self.transaction.insert(quad.as_ref())?;
146                }
147            }
148            bnodes.clear();
149        }
150        Ok(())
151    }
152
153    fn eval_load(&mut self, from: &NamedNode, to: &GraphName) -> Result<(), EvaluationError> {
154        let (content_type, body) = self
155            .client
156            .get(
157                from.as_str(),
158                "application/n-triples, text/turtle, application/rdf+xml",
159            )
160            .map_err(|e| EvaluationError::Service(Box::new(e)))?;
161        let format = RdfFormat::from_media_type(&content_type)
162            .ok_or_else(|| EvaluationError::UnsupportedContentType(content_type))?;
163        let to_graph_name = match to {
164            GraphName::NamedNode(graph_name) => graph_name.into(),
165            GraphName::DefaultGraph => GraphNameRef::DefaultGraph,
166        };
167        let client = self.client.clone();
168        let parser = RdfParser::from_format(format)
169            .rename_blank_nodes()
170            .without_named_graphs()
171            .with_default_graph(to_graph_name)
172            .with_base_iri(from.as_str())
173            .map_err(|e| {
174                EvaluationError::Service(Box::new(io::Error::new(
175                    io::ErrorKind::InvalidInput,
176                    format!("Invalid URL: {from}: {e}"),
177                )))
178            })?
179            .for_reader(body)
180            .with_document_loader(move |url| {
181                let (content_type, mut body) = client.get(
182                    url,
183                    "application/n-triples, text/turtle, application/rdf+xml, application/ld+json",
184                )?;
185                let mut content = Vec::new();
186                body.read_to_end(&mut content)?;
187                Ok(LoadedDocument {
188                    url: url.into(),
189                    content,
190                    format: RdfFormat::from_media_type(&content_type)
191                        .ok_or_else(|| EvaluationError::UnsupportedContentType(content_type))?,
192                })
193            });
194        for q in parser {
195            self.transaction.insert(q?.as_ref())?;
196        }
197        Ok(())
198    }
199
200    fn eval_create(&mut self, graph_name: &NamedNode, silent: bool) -> Result<(), EvaluationError> {
201        if self.transaction.insert_named_graph(graph_name.into())? || silent {
202            Ok(())
203        } else {
204            Err(EvaluationError::GraphAlreadyExists(graph_name.clone()))
205        }
206    }
207
208    fn eval_clear(&mut self, graph: &GraphTarget, silent: bool) -> Result<(), EvaluationError> {
209        match graph {
210            GraphTarget::NamedNode(graph_name) => {
211                if self
212                    .transaction
213                    .reader()
214                    .contains_named_graph(&graph_name.as_ref().into())?
215                {
216                    Ok(self.transaction.clear_graph(graph_name.into())?)
217                } else if silent {
218                    Ok(())
219                } else {
220                    Err(EvaluationError::GraphDoesNotExist(graph_name.clone()))
221                }
222            }
223            GraphTarget::DefaultGraph => {
224                self.transaction.clear_graph(GraphNameRef::DefaultGraph)?;
225                Ok(())
226            }
227            GraphTarget::NamedGraphs => Ok(self.transaction.clear_all_named_graphs()?),
228            GraphTarget::AllGraphs => Ok(self.transaction.clear_all_graphs()?),
229        }
230    }
231
232    fn eval_drop(&mut self, graph: &GraphTarget, silent: bool) -> Result<(), EvaluationError> {
233        match graph {
234            GraphTarget::NamedNode(graph_name) => {
235                if self.transaction.remove_named_graph(graph_name.into())? || silent {
236                    Ok(())
237                } else {
238                    Err(EvaluationError::GraphDoesNotExist(graph_name.clone()))
239                }
240            }
241            GraphTarget::DefaultGraph => {
242                Ok(self.transaction.clear_graph(GraphNameRef::DefaultGraph)?)
243            }
244            GraphTarget::NamedGraphs => Ok(self.transaction.remove_all_named_graphs()?),
245            GraphTarget::AllGraphs => Ok(self.transaction.clear()?),
246        }
247    }
248
249    fn convert_quad(quad: &Quad, bnodes: &mut FxHashMap<BlankNode, BlankNode>) -> OxQuad {
250        OxQuad {
251            subject: match &quad.subject {
252                Subject::NamedNode(subject) => subject.clone().into(),
253                Subject::BlankNode(subject) => Self::convert_blank_node(subject, bnodes).into(),
254                Subject::Triple(subject) => Self::convert_triple(subject, bnodes).into(),
255            },
256            predicate: quad.predicate.clone(),
257            object: match &quad.object {
258                Term::NamedNode(object) => object.clone().into(),
259                Term::BlankNode(object) => Self::convert_blank_node(object, bnodes).into(),
260                Term::Literal(object) => object.clone().into(),
261                Term::Triple(subject) => Self::convert_triple(subject, bnodes).into(),
262            },
263            graph_name: match &quad.graph_name {
264                GraphName::NamedNode(graph_name) => graph_name.clone().into(),
265                GraphName::DefaultGraph => OxGraphName::DefaultGraph,
266            },
267        }
268    }
269
270    fn convert_triple(triple: &Triple, bnodes: &mut FxHashMap<BlankNode, BlankNode>) -> Triple {
271        Triple {
272            subject: match &triple.subject {
273                Subject::NamedNode(subject) => subject.clone().into(),
274                Subject::BlankNode(subject) => Self::convert_blank_node(subject, bnodes).into(),
275                Subject::Triple(subject) => Self::convert_triple(subject, bnodes).into(),
276            },
277            predicate: triple.predicate.clone(),
278            object: match &triple.object {
279                Term::NamedNode(object) => object.clone().into(),
280                Term::BlankNode(object) => Self::convert_blank_node(object, bnodes).into(),
281                Term::Literal(object) => object.clone().into(),
282                Term::Triple(subject) => Self::convert_triple(subject, bnodes).into(),
283            },
284        }
285    }
286
287    fn convert_blank_node(
288        node: &BlankNode,
289        bnodes: &mut FxHashMap<BlankNode, BlankNode>,
290    ) -> BlankNode {
291        bnodes.entry(node.clone()).or_default().clone()
292    }
293
294    fn convert_ground_quad(quad: &GroundQuad) -> OxQuad {
295        OxQuad {
296            subject: match &quad.subject {
297                GroundSubject::NamedNode(subject) => subject.clone().into(),
298                GroundSubject::Triple(subject) => Self::convert_ground_triple(subject).into(),
299            },
300            predicate: quad.predicate.clone(),
301            object: match &quad.object {
302                GroundTerm::NamedNode(object) => object.clone().into(),
303                GroundTerm::Literal(object) => object.clone().into(),
304                GroundTerm::Triple(subject) => Self::convert_ground_triple(subject).into(),
305            },
306            graph_name: match &quad.graph_name {
307                GraphName::NamedNode(graph_name) => graph_name.clone().into(),
308                GraphName::DefaultGraph => OxGraphName::DefaultGraph,
309            },
310        }
311    }
312
313    fn convert_ground_triple(triple: &GroundTriple) -> Triple {
314        Triple {
315            subject: match &triple.subject {
316                GroundSubject::NamedNode(subject) => subject.clone().into(),
317                GroundSubject::Triple(subject) => Self::convert_ground_triple(subject).into(),
318            },
319            predicate: triple.predicate.clone(),
320            object: match &triple.object {
321                GroundTerm::NamedNode(object) => object.clone().into(),
322                GroundTerm::Literal(object) => object.clone().into(),
323                GroundTerm::Triple(subject) => Self::convert_ground_triple(subject).into(),
324            },
325        }
326    }
327
328    fn fill_quad_pattern(
329        quad: &QuadPattern,
330        solution: &QuerySolution,
331        bnodes: &mut FxHashMap<BlankNode, BlankNode>,
332    ) -> Option<OxQuad> {
333        Some(OxQuad {
334            subject: match Self::fill_term_or_var(&quad.subject, solution, bnodes)? {
335                Term::NamedNode(node) => node.into(),
336                Term::BlankNode(node) => node.into(),
337                Term::Triple(triple) => triple.into(),
338                Term::Literal(_) => return None,
339            },
340            predicate: Self::fill_named_node_or_var(&quad.predicate, solution)?,
341            object: Self::fill_term_or_var(&quad.object, solution, bnodes)?,
342            graph_name: Self::fill_graph_name_or_var(&quad.graph_name, solution)?,
343        })
344    }
345
346    fn fill_term_or_var(
347        term: &TermPattern,
348        solution: &QuerySolution,
349        bnodes: &mut FxHashMap<BlankNode, BlankNode>,
350    ) -> Option<Term> {
351        Some(match term {
352            TermPattern::NamedNode(term) => term.clone().into(),
353            TermPattern::BlankNode(bnode) => Self::convert_blank_node(bnode, bnodes).into(),
354            TermPattern::Literal(term) => term.clone().into(),
355            TermPattern::Triple(triple) => {
356                Self::fill_triple_pattern(triple, solution, bnodes)?.into()
357            }
358            TermPattern::Variable(v) => solution.get(v)?.clone(),
359        })
360    }
361
362    fn fill_named_node_or_var(
363        term: &NamedNodePattern,
364        solution: &QuerySolution,
365    ) -> Option<NamedNode> {
366        Some(match term {
367            NamedNodePattern::NamedNode(term) => term.clone(),
368            NamedNodePattern::Variable(v) => {
369                if let Term::NamedNode(s) = solution.get(v)? {
370                    s.clone()
371                } else {
372                    return None;
373                }
374            }
375        })
376    }
377
378    fn fill_graph_name_or_var(
379        term: &GraphNamePattern,
380        solution: &QuerySolution,
381    ) -> Option<OxGraphName> {
382        Some(match term {
383            GraphNamePattern::NamedNode(term) => term.clone().into(),
384            GraphNamePattern::DefaultGraph => OxGraphName::DefaultGraph,
385            GraphNamePattern::Variable(v) => match solution.get(v)? {
386                Term::NamedNode(node) => node.clone().into(),
387                Term::BlankNode(node) => node.clone().into(),
388                Term::Triple(_) | Term::Literal(_) => return None,
389            },
390        })
391    }
392
393    fn fill_triple_pattern(
394        triple: &TriplePattern,
395        solution: &QuerySolution,
396        bnodes: &mut FxHashMap<BlankNode, BlankNode>,
397    ) -> Option<Triple> {
398        Some(Triple {
399            subject: match Self::fill_term_or_var(&triple.subject, solution, bnodes)? {
400                Term::NamedNode(node) => node.into(),
401                Term::BlankNode(node) => node.into(),
402                Term::Triple(triple) => triple.into(),
403                Term::Literal(_) => return None,
404            },
405            predicate: Self::fill_named_node_or_var(&triple.predicate, solution)?,
406            object: Self::fill_term_or_var(&triple.object, solution, bnodes)?,
407        })
408    }
409    fn fill_ground_quad_pattern(
410        quad: &GroundQuadPattern,
411        solution: &QuerySolution,
412    ) -> Option<OxQuad> {
413        Some(OxQuad {
414            subject: match Self::fill_ground_term_or_var(&quad.subject, solution)? {
415                Term::NamedNode(node) => node.into(),
416                Term::BlankNode(node) => node.into(),
417                Term::Triple(triple) => triple.into(),
418                Term::Literal(_) => return None,
419            },
420            predicate: Self::fill_named_node_or_var(&quad.predicate, solution)?,
421            object: Self::fill_ground_term_or_var(&quad.object, solution)?,
422            graph_name: Self::fill_graph_name_or_var(&quad.graph_name, solution)?,
423        })
424    }
425
426    fn fill_ground_term_or_var(term: &GroundTermPattern, solution: &QuerySolution) -> Option<Term> {
427        Some(match term {
428            GroundTermPattern::NamedNode(term) => term.clone().into(),
429            GroundTermPattern::Literal(term) => term.clone().into(),
430            GroundTermPattern::Triple(triple) => {
431                Self::fill_ground_triple_pattern(triple, solution)?.into()
432            }
433            GroundTermPattern::Variable(v) => solution.get(v)?.clone(),
434        })
435    }
436
437    fn fill_ground_triple_pattern(
438        triple: &GroundTriplePattern,
439        solution: &QuerySolution,
440    ) -> Option<Triple> {
441        Some(Triple {
442            subject: match Self::fill_ground_term_or_var(&triple.subject, solution)? {
443                Term::NamedNode(node) => node.into(),
444                Term::BlankNode(node) => node.into(),
445                Term::Triple(triple) => triple.into(),
446                Term::Literal(_) => return None,
447            },
448            predicate: Self::fill_named_node_or_var(&triple.predicate, solution)?,
449            object: Self::fill_ground_term_or_var(&triple.object, solution)?,
450        })
451    }
452}