Skip to main content

oxigraph/sparql/
update.rs

1#[cfg(feature = "http-client")]
2use crate::io::{RdfFormat, RdfParser};
3use crate::model::{GraphName as OxGraphName, GraphNameRef, Quad as OxQuad};
4#[expect(deprecated)]
5use crate::sparql::algebra::Update;
6use crate::sparql::dataset::DatasetView;
7use crate::sparql::error::UpdateEvaluationError;
8#[cfg(feature = "http-client")]
9use crate::sparql::http::Client;
10use crate::storage::{Storage, StorageError, StorageReadableTransaction, StorageTransaction};
11use crate::store::{Store, Transaction};
12use oxiri::Iri;
13#[cfg(feature = "http-client")]
14use oxrdfio::LoadedDocument;
15use rustc_hash::FxHashMap;
16use spareval::{DeleteInsertQuad, QueryDatasetSpecification, QueryEvaluator};
17use spargebra::GraphUpdateOperation;
18use spargebra::algebra::{GraphPattern, GraphTarget};
19use spargebra::term::{
20    BlankNode, GraphName, GroundQuad, GroundQuadPattern, GroundTerm, NamedNode, NamedOrBlankNode,
21    Quad, QuadPattern, Term,
22};
23#[cfg(feature = "rdf-12")]
24use spargebra::term::{GroundTriple, Triple};
25#[cfg(feature = "http-client")]
26use std::io::Read;
27#[cfg(feature = "http-client")]
28use std::time::Duration;
29
30/// A prepared SPARQL update.
31///
32/// Usage example:
33/// ```
34/// use oxigraph::sparql::SparqlEvaluator;
35/// use oxigraph::store::Store;
36///
37/// let prepared_update = SparqlEvaluator::new().parse_update(
38///     "INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }",
39/// )?;
40/// prepared_update.on_store(&Store::new()?).execute()?;
41/// # Ok::<_, Box<dyn std::error::Error>>(())
42/// ```
43#[derive(Clone)]
44#[must_use]
45pub struct PreparedSparqlUpdate {
46    evaluator: QueryEvaluator,
47    update: spargebra::Update,
48    using_datasets: Vec<Option<QueryDatasetSpecification>>,
49    #[cfg(feature = "http-client")]
50    http_timeout: Option<Duration>,
51    #[cfg(feature = "http-client")]
52    http_redirection_limit: usize,
53}
54
55impl PreparedSparqlUpdate {
56    #[expect(deprecated)]
57    pub(crate) fn new(
58        evaluator: QueryEvaluator,
59        update: Update,
60        #[cfg(feature = "http-client")] http_timeout: Option<Duration>,
61        #[cfg(feature = "http-client")] http_redirection_limit: usize,
62    ) -> Self {
63        Self {
64            evaluator,
65            update: update.inner,
66            using_datasets: update.using_datasets,
67            #[cfg(feature = "http-client")]
68            http_timeout,
69            #[cfg(feature = "http-client")]
70            http_redirection_limit,
71        }
72    }
73
74    /// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) in [DELETE/INSERT operations](https://www.w3.org/TR/sparql11-update/#deleteInsert).
75    #[inline]
76    pub fn using_datasets(&self) -> impl Iterator<Item = &QueryDatasetSpecification> {
77        self.using_datasets.iter().filter_map(Option::as_ref)
78    }
79
80    /// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) in [DELETE/INSERT operations](https://www.w3.org/TR/sparql11-update/#deleteInsert).
81    #[inline]
82    pub fn using_datasets_mut(&mut self) -> impl Iterator<Item = &mut QueryDatasetSpecification> {
83        self.using_datasets.iter_mut().filter_map(Option::as_mut)
84    }
85
86    /// Bind the prepared update to the [`Store`] it should be evaluated on.
87    ///
88    /// Usage example:
89    /// ```
90    /// use oxigraph::sparql::SparqlEvaluator;
91    /// use oxigraph::store::Store;
92    ///
93    /// let prepared_update = SparqlEvaluator::new().parse_update(
94    ///     "INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }",
95    /// )?;
96    /// prepared_update.on_store(&Store::new()?).execute()?;
97    /// # Ok::<_, Box<dyn std::error::Error>>(())
98    /// ```
99    pub fn on_store(self, store: &Store) -> BoundPreparedSparqlUpdate<'_, '_> {
100        let transaction = if update_requires_read(&self.update) {
101            store
102                .storage()
103                .start_readable_transaction()
104                .map(UpdateTransaction::OwnedReadable)
105        } else {
106            let storage = store.storage();
107            storage
108                .start_transaction()
109                .map(|transaction| UpdateTransaction::Owned(transaction, storage))
110        };
111        BoundPreparedSparqlUpdate {
112            evaluator: self.evaluator,
113            update: self.update,
114            using_datasets: self.using_datasets,
115            #[cfg(feature = "http-client")]
116            http_timeout: self.http_timeout,
117            #[cfg(feature = "http-client")]
118            http_redirection_limit: self.http_redirection_limit,
119            transaction,
120        }
121    }
122
123    /// Bind the prepared update to the [`Transaction`] it should be evaluated on.
124    ///
125    /// Usage example:
126    /// ```
127    /// use oxigraph::sparql::SparqlEvaluator;
128    /// use oxigraph::store::Store;
129    ///
130    /// let prepared_update = SparqlEvaluator::new().parse_update(
131    ///     "INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }",
132    /// )?;
133    /// let store = Store::new()?;
134    /// let mut transaction = store.start_transaction()?;
135    /// prepared_update.on_transaction(&mut transaction).execute()?;
136    /// # Ok::<_, Box<dyn std::error::Error>>(())
137    /// ```
138    pub fn on_transaction<'a, 'b: 'a>(
139        self,
140        transaction: &'a mut Transaction<'b>,
141    ) -> BoundPreparedSparqlUpdate<'a, 'b> {
142        BoundPreparedSparqlUpdate {
143            evaluator: self.evaluator,
144            update: self.update,
145            using_datasets: self.using_datasets,
146            #[cfg(feature = "http-client")]
147            http_timeout: self.http_timeout,
148            #[cfg(feature = "http-client")]
149            http_redirection_limit: self.http_redirection_limit,
150            transaction: Ok(UpdateTransaction::BorrowedReadable(transaction.inner_mut())),
151        }
152    }
153}
154
155/// A prepared SPARQL query bound to a storage, ready to be executed.
156///
157///
158/// Usage example:
159/// ```
160/// use oxigraph::sparql::SparqlEvaluator;
161/// use oxigraph::store::Store;
162///
163/// let store = Store::new()?;
164/// let prepared_update = SparqlEvaluator::new()
165///     .parse_update(
166///         "INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }",
167///     )?
168///     .on_store(&store);
169/// prepared_update.execute()?;
170/// # Ok::<_, Box<dyn std::error::Error>>(())
171/// ```
172#[must_use]
173pub struct BoundPreparedSparqlUpdate<'a, 'b> {
174    evaluator: QueryEvaluator,
175    update: spargebra::Update,
176    using_datasets: Vec<Option<QueryDatasetSpecification>>,
177    #[cfg(feature = "http-client")]
178    http_timeout: Option<Duration>,
179    #[cfg(feature = "http-client")]
180    http_redirection_limit: usize,
181    transaction: Result<UpdateTransaction<'a, 'b>, StorageError>,
182}
183
184impl BoundPreparedSparqlUpdate<'_, '_> {
185    /// Evaluate the update against the given store.
186    pub fn execute(self) -> Result<(), UpdateEvaluationError> {
187        match self.transaction? {
188            UpdateTransaction::OwnedReadable(mut transaction) => {
189                ReadableUpdateEvaluator {
190                    transaction: &mut transaction,
191                    base_iri: self.update.base_iri.clone(),
192                    query_evaluator: self.evaluator,
193                    #[cfg(feature = "http-client")]
194                    client: Client::new(self.http_timeout, self.http_redirection_limit),
195                }
196                .eval_all(&self.update.operations, &self.using_datasets)?;
197                transaction.commit()?;
198                Ok(())
199            }
200            UpdateTransaction::BorrowedReadable(transaction) => ReadableUpdateEvaluator {
201                transaction,
202                base_iri: self.update.base_iri.clone(),
203                query_evaluator: self.evaluator,
204                #[cfg(feature = "http-client")]
205                client: Client::new(self.http_timeout, self.http_redirection_limit),
206            }
207            .eval_all(&self.update.operations, &self.using_datasets),
208            UpdateTransaction::Owned(mut transaction, storage) => {
209                WriteOnlyUpdateEvaluator {
210                    transaction: &mut transaction,
211                    storage_for_initial_read: Some(storage),
212                    base_iri: self.update.base_iri.clone(),
213                    query_evaluator: self.evaluator,
214                    #[cfg(feature = "http-client")]
215                    client: Client::new(self.http_timeout, self.http_redirection_limit),
216                }
217                .eval_all(&self.update.operations, &self.using_datasets)?;
218                transaction.commit()?;
219                Ok(())
220            }
221        }
222    }
223}
224
225enum UpdateTransaction<'a, 'b> {
226    OwnedReadable(StorageReadableTransaction<'b>),
227    BorrowedReadable(&'a mut StorageReadableTransaction<'b>),
228    Owned(StorageTransaction<'b>, &'b Storage),
229}
230
231struct ReadableUpdateEvaluator<'a, 'b> {
232    transaction: &'a mut StorageReadableTransaction<'b>,
233    base_iri: Option<Iri<String>>,
234    query_evaluator: QueryEvaluator,
235    #[cfg(feature = "http-client")]
236    client: Client,
237}
238
239impl<'a, 'b: 'a> ReadableUpdateEvaluator<'a, 'b> {
240    fn eval_all(
241        &mut self,
242        updates: &[GraphUpdateOperation],
243        using_datasets: &[Option<QueryDatasetSpecification>],
244    ) -> Result<(), UpdateEvaluationError> {
245        for (update, using_dataset) in updates.iter().zip(using_datasets) {
246            self.eval(update, using_dataset)?;
247        }
248        Ok(())
249    }
250
251    fn eval(
252        &mut self,
253        update: &GraphUpdateOperation,
254        using_dataset: &Option<QueryDatasetSpecification>,
255    ) -> Result<(), UpdateEvaluationError> {
256        match update {
257            GraphUpdateOperation::InsertData { data } => {
258                self.eval_insert_data(data);
259                Ok(())
260            }
261            GraphUpdateOperation::DeleteData { data } => {
262                self.eval_delete_data(data);
263                Ok(())
264            }
265            GraphUpdateOperation::DeleteInsert {
266                delete,
267                insert,
268                pattern,
269                ..
270            } => self.eval_delete_insert(
271                delete,
272                insert,
273                using_dataset
274                    .as_ref()
275                    .unwrap_or(&QueryDatasetSpecification::new()),
276                pattern,
277            ),
278            GraphUpdateOperation::Load {
279                silent,
280                source,
281                destination,
282            } => {
283                if let Err(error) = self.eval_load(source, destination) {
284                    if *silent { Ok(()) } else { Err(error) }
285                } else {
286                    Ok(())
287                }
288            }
289            GraphUpdateOperation::Clear { graph, silent } => self.eval_clear(graph, *silent),
290            GraphUpdateOperation::Create { graph, silent } => self.eval_create(graph, *silent),
291            GraphUpdateOperation::Drop { graph, silent } => self.eval_drop(graph, *silent),
292        }
293    }
294
295    fn eval_insert_data(&mut self, data: &[Quad]) {
296        let mut bnodes = FxHashMap::default();
297        for quad in data {
298            let quad = convert_quad(quad, &mut bnodes);
299            self.transaction.insert(quad.as_ref());
300        }
301    }
302
303    fn eval_delete_data(&mut self, data: &[GroundQuad]) {
304        for quad in data {
305            let quad = convert_ground_quad(quad);
306            self.transaction.remove(quad.as_ref());
307        }
308    }
309
310    fn eval_delete_insert(
311        &mut self,
312        delete: &[GroundQuadPattern],
313        insert: &[QuadPattern],
314        using: &QueryDatasetSpecification,
315        algebra: &GraphPattern,
316    ) -> Result<(), UpdateEvaluationError> {
317        let mut prepared = self.query_evaluator.prepare_delete_insert(
318            delete.to_vec(),
319            insert.to_vec(),
320            self.base_iri.clone(),
321            None,
322            algebra,
323        );
324        *prepared.dataset_mut() = using.clone();
325        let mutations = prepared
326            .execute(DatasetView::new(self.transaction.reader()))?
327            .collect::<Result<Vec<_>, _>>()?;
328        for mutation in mutations {
329            match mutation {
330                DeleteInsertQuad::Delete(quad) => self.transaction.remove(quad.as_ref()),
331                DeleteInsertQuad::Insert(quad) => self.transaction.insert(quad.as_ref()),
332            }
333        }
334        Ok(())
335    }
336
337    fn eval_load(&mut self, from: &NamedNode, to: &GraphName) -> Result<(), UpdateEvaluationError> {
338        eval_load(
339            from,
340            to,
341            #[cfg(feature = "http-client")]
342            &self.client,
343            |q| self.transaction.insert(q.as_ref()),
344        )
345    }
346
347    fn eval_create(
348        &mut self,
349        graph_name: &NamedNode,
350        silent: bool,
351    ) -> Result<(), UpdateEvaluationError> {
352        if self
353            .transaction
354            .reader()
355            .contains_named_graph(&graph_name.as_ref().into())?
356        {
357            if silent {
358                Ok(())
359            } else {
360                Err(UpdateEvaluationError::GraphAlreadyExists(
361                    graph_name.clone(),
362                ))
363            }
364        } else {
365            self.transaction.insert_named_graph(graph_name.into());
366            Ok(())
367        }
368    }
369
370    fn eval_clear(
371        &mut self,
372        graph: &GraphTarget,
373        silent: bool,
374    ) -> Result<(), UpdateEvaluationError> {
375        match graph {
376            GraphTarget::NamedNode(graph_name) => {
377                if self
378                    .transaction
379                    .reader()
380                    .contains_named_graph(&graph_name.as_ref().into())?
381                {
382                    Ok(self.transaction.clear_graph(graph_name.into())?)
383                } else if silent {
384                    Ok(())
385                } else {
386                    Err(UpdateEvaluationError::GraphDoesNotExist(graph_name.clone()))
387                }
388            }
389            GraphTarget::DefaultGraph => {
390                self.transaction.clear_graph(GraphNameRef::DefaultGraph)?;
391                Ok(())
392            }
393            GraphTarget::NamedGraphs => Ok(self.transaction.clear_all_named_graphs()?),
394            GraphTarget::AllGraphs => Ok(self.transaction.clear_all_graphs()?),
395        }
396    }
397
398    fn eval_drop(
399        &mut self,
400        graph: &GraphTarget,
401        silent: bool,
402    ) -> Result<(), UpdateEvaluationError> {
403        match graph {
404            GraphTarget::NamedNode(graph_name) => {
405                if self
406                    .transaction
407                    .reader()
408                    .contains_named_graph(&graph_name.as_ref().into())?
409                {
410                    self.transaction.remove_named_graph(graph_name.into())?;
411                    Ok(())
412                } else if silent {
413                    Ok(())
414                } else {
415                    Err(UpdateEvaluationError::GraphDoesNotExist(graph_name.clone()))
416                }
417            }
418            GraphTarget::DefaultGraph => {
419                Ok(self.transaction.clear_graph(GraphNameRef::DefaultGraph)?)
420            }
421            GraphTarget::NamedGraphs => Ok(self.transaction.remove_all_named_graphs()?),
422            GraphTarget::AllGraphs => Ok(self.transaction.clear()?),
423        }
424    }
425}
426
427fn update_requires_read(update: &spargebra::Update) -> bool {
428    for (i, op) in update.operations.iter().enumerate() {
429        match op {
430            GraphUpdateOperation::InsertData { .. }
431            | GraphUpdateOperation::DeleteData { .. }
432            | GraphUpdateOperation::Create { silent: true, .. }
433            | GraphUpdateOperation::Clear {
434                graph: GraphTarget::DefaultGraph | GraphTarget::NamedGraphs | GraphTarget::AllGraphs,
435                ..
436            }
437            | GraphUpdateOperation::Drop {
438                graph: GraphTarget::DefaultGraph | GraphTarget::NamedGraphs | GraphTarget::AllGraphs,
439                ..
440            } => (),
441            GraphUpdateOperation::DeleteInsert { .. } if i == 0 => (),
442            _ => return true,
443        }
444    }
445    false
446}
447
448struct WriteOnlyUpdateEvaluator<'a, 'b> {
449    transaction: &'a mut StorageTransaction<'b>,
450    storage_for_initial_read: Option<&'b Storage>,
451    base_iri: Option<Iri<String>>,
452    query_evaluator: QueryEvaluator,
453    #[cfg(feature = "http-client")]
454    client: Client,
455}
456
457impl WriteOnlyUpdateEvaluator<'_, '_> {
458    fn eval_all(
459        &mut self,
460        updates: &[GraphUpdateOperation],
461        using_datasets: &[Option<QueryDatasetSpecification>],
462    ) -> Result<(), UpdateEvaluationError> {
463        for (update, using_dataset) in updates.iter().zip(using_datasets) {
464            self.eval(update, using_dataset)?;
465            self.storage_for_initial_read.take(); // We unset the initial reader because we have likely mutated the store state.
466        }
467        Ok(())
468    }
469
470    fn eval(
471        &mut self,
472        update: &GraphUpdateOperation,
473        using_dataset: &Option<QueryDatasetSpecification>,
474    ) -> Result<(), UpdateEvaluationError> {
475        match update {
476            GraphUpdateOperation::InsertData { data } => {
477                self.eval_insert_data(data);
478                Ok(())
479            }
480            GraphUpdateOperation::DeleteData { data } => {
481                self.eval_delete_data(data);
482                Ok(())
483            }
484            GraphUpdateOperation::DeleteInsert {
485                delete,
486                insert,
487                pattern,
488                ..
489            } => self.eval_delete_insert(
490                delete,
491                insert,
492                using_dataset
493                    .as_ref()
494                    .unwrap_or(&QueryDatasetSpecification::new()),
495                pattern,
496            ),
497            GraphUpdateOperation::Load {
498                silent,
499                source,
500                destination,
501            } => {
502                if let Err(error) = self.eval_load(source, destination) {
503                    if *silent { Ok(()) } else { Err(error) }
504                } else {
505                    Ok(())
506                }
507            }
508            GraphUpdateOperation::Clear { graph, silent } => self.eval_clear(graph, *silent),
509            GraphUpdateOperation::Create { graph, silent } => self.eval_create(graph, *silent),
510            GraphUpdateOperation::Drop { graph, silent } => self.eval_drop(graph, *silent),
511        }
512    }
513
514    fn eval_insert_data(&mut self, data: &[Quad]) {
515        let mut bnodes = FxHashMap::default();
516        for quad in data {
517            let quad = convert_quad(quad, &mut bnodes);
518            self.transaction.insert(quad.as_ref());
519        }
520    }
521
522    fn eval_delete_data(&mut self, data: &[GroundQuad]) {
523        for quad in data {
524            let quad = convert_ground_quad(quad);
525            self.transaction.remove(quad.as_ref());
526        }
527    }
528
529    fn eval_delete_insert(
530        &mut self,
531        delete: &[GroundQuadPattern],
532        insert: &[QuadPattern],
533        using: &QueryDatasetSpecification,
534        algebra: &GraphPattern,
535    ) -> Result<(), UpdateEvaluationError> {
536        let Some(storage) = self.storage_for_initial_read.take() else {
537            return Err(UpdateEvaluationError::Unexpected(
538                "It is not possible to evaluate delete/insert operations on a write-only transaction after other update operations".into(),
539            ));
540        };
541        let mut prepared = self.query_evaluator.prepare_delete_insert(
542            delete.to_vec(),
543            insert.to_vec(),
544            self.base_iri.clone(),
545            None,
546            algebra,
547        );
548        *prepared.dataset_mut() = using.clone();
549        let mutations = prepared
550            .execute(DatasetView::new(storage.snapshot()))?
551            .collect::<Result<Vec<_>, _>>()?;
552        for mutation in mutations {
553            match mutation {
554                DeleteInsertQuad::Delete(quad) => self.transaction.remove(quad.as_ref()),
555                DeleteInsertQuad::Insert(quad) => self.transaction.insert(quad.as_ref()),
556            }
557        }
558        Ok(())
559    }
560
561    fn eval_load(&mut self, from: &NamedNode, to: &GraphName) -> Result<(), UpdateEvaluationError> {
562        eval_load(
563            from,
564            to,
565            #[cfg(feature = "http-client")]
566            &self.client,
567            |q| self.transaction.insert(q.as_ref()),
568        )
569    }
570
571    fn eval_create(
572        &mut self,
573        graph_name: &NamedNode,
574        silent: bool,
575    ) -> Result<(), UpdateEvaluationError> {
576        if !silent {
577            return Err(UpdateEvaluationError::Unexpected(
578                "Not possible to create a named graph using a write-only transaction when SILENT option is not set".into(),
579            ));
580        }
581        self.transaction.insert_named_graph(graph_name.into());
582        Ok(())
583    }
584
585    fn eval_clear(
586        &mut self,
587        graph: &GraphTarget,
588        _silent: bool,
589    ) -> Result<(), UpdateEvaluationError> {
590        match graph {
591            GraphTarget::NamedNode(_) => Err(UpdateEvaluationError::Unexpected(
592                "Not possible to clear a named graph using a write-only transaction".into(),
593            )),
594            GraphTarget::DefaultGraph => {
595                self.transaction.clear_default_graph();
596                Ok(())
597            }
598            GraphTarget::NamedGraphs => {
599                self.transaction.clear_all_named_graphs();
600                Ok(())
601            }
602            GraphTarget::AllGraphs => {
603                self.transaction.clear_all_graphs();
604                Ok(())
605            }
606        }
607    }
608
609    fn eval_drop(
610        &mut self,
611        graph: &GraphTarget,
612        _silent: bool,
613    ) -> Result<(), UpdateEvaluationError> {
614        match graph {
615            GraphTarget::NamedNode(_) => Err(UpdateEvaluationError::Unexpected(
616                "Not possible to drop a named graph using a write-only transaction".into(),
617            )),
618            GraphTarget::DefaultGraph => {
619                self.transaction.clear_default_graph();
620                Ok(())
621            }
622            GraphTarget::NamedGraphs => {
623                self.transaction.remove_all_named_graphs();
624                Ok(())
625            }
626            GraphTarget::AllGraphs => {
627                self.transaction.clear();
628                Ok(())
629            }
630        }
631    }
632}
633
634#[cfg(feature = "http-client")]
635fn eval_load(
636    from: &NamedNode,
637    to: &GraphName,
638    client: &Client,
639    mut insert: impl FnMut(OxQuad),
640) -> Result<(), UpdateEvaluationError> {
641    let (content_type, body) = client
642        .get(
643            from.as_str(),
644            "application/n-triples, text/turtle, application/rdf+xml",
645        )
646        .map_err(|e| UpdateEvaluationError::Service(Box::new(e)))?;
647    let format = RdfFormat::from_media_type(&content_type)
648        .ok_or_else(|| UpdateEvaluationError::UnsupportedContentType(content_type))?;
649    let to_graph_name = match to {
650        GraphName::NamedNode(graph_name) => graph_name.into(),
651        GraphName::DefaultGraph => GraphNameRef::DefaultGraph,
652    };
653    let client = client.clone();
654    let parser = RdfParser::from_format(format)
655        .rename_blank_nodes()
656        .without_named_graphs()
657        .with_default_graph(to_graph_name)
658        .with_base_iri(from.as_str())
659        .map_err(|e| UpdateEvaluationError::Unexpected(format!("Invalid URL: {from}: {e}").into()))?
660        .for_reader(body)
661        .with_document_loader(move |url| {
662            let (content_type, mut body) = client.get(
663                url,
664                "application/n-triples, text/turtle, application/rdf+xml, application/ld+json",
665            )?;
666            let mut content = Vec::new();
667            body.read_to_end(&mut content)?;
668            Ok(LoadedDocument {
669                url: url.into(),
670                content,
671                format: RdfFormat::from_media_type(&content_type)
672                    .ok_or_else(|| UpdateEvaluationError::UnsupportedContentType(content_type))?,
673            })
674        });
675    for q in parser {
676        insert(q?);
677    }
678    Ok(())
679}
680
681#[cfg(not(feature = "http-client"))]
682fn eval_load(
683    _from: &NamedNode,
684    _to: &GraphName,
685    _insert: impl FnMut(OxQuad),
686) -> Result<(), UpdateEvaluationError> {
687    Err(UpdateEvaluationError::Unexpected(
688        "HTTP client is not available. Enable the feature 'http-client'".into(),
689    ))
690}
691
692fn convert_quad(quad: &Quad, bnodes: &mut FxHashMap<BlankNode, BlankNode>) -> OxQuad {
693    OxQuad {
694        subject: match &quad.subject {
695            NamedOrBlankNode::NamedNode(subject) => subject.clone().into(),
696            NamedOrBlankNode::BlankNode(subject) => convert_blank_node(subject, bnodes).into(),
697        },
698        predicate: quad.predicate.clone(),
699        object: match &quad.object {
700            Term::NamedNode(object) => object.clone().into(),
701            Term::BlankNode(object) => convert_blank_node(object, bnodes).into(),
702            Term::Literal(object) => object.clone().into(),
703            #[cfg(feature = "rdf-12")]
704            Term::Triple(subject) => convert_triple(subject, bnodes).into(),
705        },
706        graph_name: match &quad.graph_name {
707            GraphName::NamedNode(graph_name) => graph_name.clone().into(),
708            GraphName::DefaultGraph => OxGraphName::DefaultGraph,
709        },
710    }
711}
712
713#[cfg(feature = "rdf-12")]
714fn convert_triple(triple: &Triple, bnodes: &mut FxHashMap<BlankNode, BlankNode>) -> Triple {
715    Triple {
716        subject: match &triple.subject {
717            NamedOrBlankNode::NamedNode(subject) => subject.clone().into(),
718            NamedOrBlankNode::BlankNode(subject) => convert_blank_node(subject, bnodes).into(),
719        },
720        predicate: triple.predicate.clone(),
721        object: match &triple.object {
722            Term::NamedNode(object) => object.clone().into(),
723            Term::BlankNode(object) => convert_blank_node(object, bnodes).into(),
724            Term::Literal(object) => object.clone().into(),
725            #[cfg(feature = "rdf-12")]
726            Term::Triple(subject) => convert_triple(subject, bnodes).into(),
727        },
728    }
729}
730
731fn convert_blank_node(node: &BlankNode, bnodes: &mut FxHashMap<BlankNode, BlankNode>) -> BlankNode {
732    bnodes.entry(node.clone()).or_default().clone()
733}
734
735fn convert_ground_quad(quad: &GroundQuad) -> OxQuad {
736    OxQuad {
737        subject: quad.subject.clone().into(),
738        predicate: quad.predicate.clone(),
739        object: match &quad.object {
740            GroundTerm::NamedNode(object) => object.clone().into(),
741            GroundTerm::Literal(object) => object.clone().into(),
742            #[cfg(feature = "rdf-12")]
743            GroundTerm::Triple(subject) => convert_ground_triple(subject).into(),
744        },
745        graph_name: match &quad.graph_name {
746            GraphName::NamedNode(graph_name) => graph_name.clone().into(),
747            GraphName::DefaultGraph => OxGraphName::DefaultGraph,
748        },
749    }
750}
751
752#[cfg(feature = "rdf-12")]
753fn convert_ground_triple(triple: &GroundTriple) -> Triple {
754    Triple {
755        subject: triple.subject.clone().into(),
756        predicate: triple.predicate.clone(),
757        object: match &triple.object {
758            GroundTerm::NamedNode(object) => object.clone().into(),
759            GroundTerm::Literal(object) => object.clone().into(),
760            #[cfg(feature = "rdf-12")]
761            GroundTerm::Triple(subject) => convert_ground_triple(subject).into(),
762        },
763    }
764}