diff --git a/lib/src/sparql/algebra.rs b/lib/src/sparql/algebra.rs index 7898c822..b28239a0 100644 --- a/lib/src/sparql/algebra.rs +++ b/lib/src/sparql/algebra.rs @@ -215,7 +215,7 @@ impl<'a> TryFrom<&'a String> for Query { pub struct Update { /// The update base IRI pub base_iri: Option>, - /// The update operations + /// The [update operations](https://www.w3.org/TR/sparql11-update/#formalModelGraphUpdate) pub operations: Vec, } diff --git a/lib/src/sparql/mod.rs b/lib/src/sparql/mod.rs index 2bd617a7..fb391465 100644 --- a/lib/src/sparql/mod.rs +++ b/lib/src/sparql/mod.rs @@ -144,6 +144,42 @@ impl QueryOptions { } } +/// Options for SPARQL update evaluation +#[derive(Clone)] +pub struct UpdateOptions { + query_options: QueryOptions, +} + +impl UpdateOptions { + /// The options related to the querying part of the updates + #[inline] + pub fn query_options(&self) -> &QueryOptions { + &self.query_options + } + + /// The options related to the querying part of the updates + #[inline] + pub fn query_options_mut(&mut self) -> &mut QueryOptions { + &mut self.query_options + } +} + +impl Default for UpdateOptions { + #[inline] + fn default() -> Self { + Self { + query_options: QueryOptions::default(), + } + } +} + +impl From for UpdateOptions { + #[inline] + fn from(query_options: QueryOptions) -> Self { + Self { query_options } + } +} + pub(crate) fn evaluate_update< R: ReadableEncodedStore + Clone + 'static, W: StrContainer + WritableEncodedStore, @@ -151,15 +187,11 @@ pub(crate) fn evaluate_update< read: R, write: &mut W, update: Update, + options: UpdateOptions, ) -> Result<(), EvaluationError> where io::Error: From>, { - SimpleUpdateEvaluator::new( - read, - write, - update.base_iri.map(Rc::new), - Rc::new(EmptyServiceHandler), - ) - .eval_all(&update.operations) + SimpleUpdateEvaluator::new(read, write, update.base_iri.map(Rc::new), options) + .eval_all(&update.operations) } diff --git a/lib/src/sparql/parser.rs b/lib/src/sparql/parser.rs index 2aa39167..456be338 100644 --- a/lib/src/sparql/parser.rs +++ b/lib/src/sparql/parser.rs @@ -1036,21 +1036,6 @@ parser! { let mut insert = insert.unwrap_or_else(Vec::new); let mut pattern = pattern; - if let Some(with) = with { - // We inject WITH everywhere - delete = delete.into_iter().map(|q| if q.graph_name.is_none() { - QuadPattern::new(q.subject, q.predicate, q.object, Some(with.clone().into())) - } else { - q - }).collect(); - insert = insert.into_iter().map(|q| if q.graph_name.is_none() { - QuadPattern::new(q.subject, q.predicate, q.object, Some(with.clone().into())) - } else { - q - }).collect(); - pattern = GraphPattern::Graph { graph_name: with.into(), inner: Box::new(pattern) }; - } - let mut using = QueryDataset::default(); if !u.is_empty() { let mut using_default = Vec::new(); @@ -1067,6 +1052,23 @@ parser! { using.set_available_named_graphs(using_named); } + if let Some(with) = with { + // We inject WITH everywhere + delete = delete.into_iter().map(|q| if q.graph_name.is_none() { + QuadPattern::new(q.subject, q.predicate, q.object, Some(with.clone().into())) + } else { + q + }).collect(); + insert = insert.into_iter().map(|q| if q.graph_name.is_none() { + QuadPattern::new(q.subject, q.predicate, q.object, Some(with.clone().into())) + } else { + q + }).collect(); + if using.is_default_dataset() { + using.set_default_graph(vec![with.into()]); + } + } + vec![GraphUpdateOperation::DeleteInsert { delete, insert, diff --git a/lib/src/sparql/update.rs b/lib/src/sparql/update.rs index a7e92ac6..dbd5337e 100644 --- a/lib/src/sparql/update.rs +++ b/lib/src/sparql/update.rs @@ -10,8 +10,7 @@ use crate::sparql::eval::SimpleEvaluator; use crate::sparql::http::Client; use crate::sparql::plan::EncodedTuple; use crate::sparql::plan_builder::PlanBuilder; -use crate::sparql::service::ServiceHandler; -use crate::sparql::{EvaluationError, Variable}; +use crate::sparql::{EvaluationError, UpdateOptions, Variable}; use crate::store::numeric_encoder::{ EncodedQuad, EncodedTerm, ReadEncoder, StrContainer, StrLookup, WriteEncoder, }; @@ -27,7 +26,7 @@ pub(crate) struct SimpleUpdateEvaluator<'a, R, W> { read: R, write: &'a mut W, base_iri: Option>>, - service_handler: Rc>, + options: UpdateOptions, client: Client, } @@ -43,13 +42,13 @@ where read: R, write: &'a mut W, base_iri: Option>>, - service_handler: Rc>, + options: UpdateOptions, ) -> Self { Self { read, write, base_iri, - service_handler, + options, client: Client::new(), } } @@ -119,7 +118,7 @@ where let evaluator = SimpleEvaluator::>::new( dataset.clone(), self.base_iri.clone(), - self.service_handler.clone(), + self.options.query_options.service_handler.clone(), ); let mut bnodes = HashMap::new(); for tuple in evaluator.eval_plan(&plan, EncodedTuple::with_capacity(variables.len())) { diff --git a/lib/src/store/memory.rs b/lib/src/store/memory.rs index 08db8a33..c7283bbe 100644 --- a/lib/src/store/memory.rs +++ b/lib/src/store/memory.rs @@ -5,6 +5,7 @@ use crate::io::{DatasetFormat, DatasetParser, GraphFormat, GraphParser}; use crate::model::*; use crate::sparql::{ evaluate_query, evaluate_update, EvaluationError, Query, QueryOptions, QueryResults, Update, + UpdateOptions, }; use crate::store::numeric_encoder::{ Decoder, ReadEncoder, StrContainer, StrEncodingAware, StrId, StrLookup, WriteEncoder, @@ -234,11 +235,21 @@ impl MemoryStore { pub fn update( &self, update: impl TryInto>, + ) -> Result<(), EvaluationError> { + self.update_opt(update, UpdateOptions::default()) + } + + /// Executes a [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/) with some options. + pub fn update_opt( + &self, + update: impl TryInto>, + options: UpdateOptions, ) -> Result<(), EvaluationError> { evaluate_update( self.clone(), &mut &*self, update.try_into().map_err(|e| e.into())?, + options, ) } diff --git a/lib/src/store/rocksdb.rs b/lib/src/store/rocksdb.rs index f104a7c1..ec4415bf 100644 --- a/lib/src/store/rocksdb.rs +++ b/lib/src/store/rocksdb.rs @@ -5,6 +5,7 @@ use crate::io::{DatasetFormat, GraphFormat}; use crate::model::*; use crate::sparql::{ evaluate_query, evaluate_update, EvaluationError, Query, QueryOptions, QueryResults, Update, + UpdateOptions, }; use crate::store::binary_encoder::*; use crate::store::numeric_encoder::{ @@ -215,12 +216,22 @@ impl RocksDbStore { pub fn update( &self, update: impl TryInto>, + ) -> Result<(), EvaluationError> { + self.update_opt(update, UpdateOptions::default()) + } + + /// Executes a [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/) with some options. + pub fn update_opt( + &self, + update: impl TryInto>, + options: UpdateOptions, ) -> Result<(), EvaluationError> { let mut writer = self.auto_batch_writer(); evaluate_update( self.clone(), &mut writer, update.try_into().map_err(|e| e.into())?, + options, )?; Ok(writer.apply()?) } diff --git a/lib/src/store/sled.rs b/lib/src/store/sled.rs index 1edce28e..772fa496 100644 --- a/lib/src/store/sled.rs +++ b/lib/src/store/sled.rs @@ -5,6 +5,7 @@ use crate::io::{DatasetFormat, GraphFormat}; use crate::model::*; use crate::sparql::{ evaluate_query, evaluate_update, EvaluationError, Query, QueryOptions, QueryResults, Update, + UpdateOptions, }; use crate::store::binary_encoder::*; use crate::store::numeric_encoder::{ @@ -207,11 +208,21 @@ impl SledStore { pub fn update( &self, update: impl TryInto>, + ) -> Result<(), EvaluationError> { + self.update_opt(update, UpdateOptions::default()) + } + + /// Executes a [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/) with some options. + pub fn update_opt( + &self, + update: impl TryInto>, + options: UpdateOptions, ) -> Result<(), EvaluationError> { evaluate_update( self.clone(), &mut &*self, update.try_into().map_err(|e| e.into())?, + options, ) } diff --git a/server/src/main.rs b/server/src/main.rs index 92a97a36..3736d7c7 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -18,15 +18,15 @@ use async_std::task::{block_on, spawn}; use http_types::{headers, Body, Error, Method, Mime, Request, Response, Result, StatusCode}; use oxigraph::io::{DatasetFormat, GraphFormat}; use oxigraph::model::{GraphName, NamedNode, NamedOrBlankNode}; +use oxigraph::sparql::algebra::GraphUpdateOperation; use oxigraph::sparql::{Query, QueryResults, QueryResultsFormat, Update}; -use std::io::BufReader; -use std::str::FromStr; -use url::form_urlencoded; - #[cfg(feature = "rocksdb")] use oxigraph::RocksDbStore as Store; #[cfg(all(feature = "sled", not(feature = "rocksdb")))] use oxigraph::SledStore as Store; +use std::io::BufReader; +use std::str::FromStr; +use url::form_urlencoded; const MAX_SPARQL_BODY_SIZE: u64 = 1_048_576; const HTML_ROOT_PAGE: &str = include_str!("../templates/query.html"); @@ -304,17 +304,36 @@ async fn evaluate_sparql_update( default_graph_uris: Vec, named_graph_uris: Vec, ) -> Result { - if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { - return Ok(simple_response( - StatusCode::BadRequest, - "using-graph-uri and using-named-graph-uri parameters are not supported yet", - )); - } - let update = Update::parse(&update, None).map_err(|e| { + let mut update = Update::parse(&update, None).map_err(|e| { let mut e = Error::from(e); e.set_status(StatusCode::BadRequest); e })?; + let default_graph_uris = default_graph_uris + .into_iter() + .map(|e| Ok(NamedNode::new(e)?.into())) + .collect::>>() + .map_err(bad_request)?; + let named_graph_uris = named_graph_uris + .into_iter() + .map(|e| Ok(NamedNode::new(e)?.into())) + .collect::>>() + .map_err(bad_request)?; + if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { + for operation in &mut update.operations { + if let GraphUpdateOperation::DeleteInsert { using, .. } = operation { + if !using.is_default_dataset() { + let result = Ok(simple_response( + StatusCode::BadRequest, + "using-graph-uri and using-named-graph-uri must not be used with a SPARQL UPDATE containing USING", + )); + return result; + } + using.set_default_graph(default_graph_uris.clone()); + using.set_available_named_graphs(named_graph_uris.clone()); + } + } + } store.update(update)?; Ok(Response::new(StatusCode::NoContent)) }