Implements SPARQL UPDATE

pull/51/head
Tpt 4 years ago
parent b762991917
commit bf3d205cf7
  1. 20
      lib/src/sparql/algebra.rs
  2. 14
      lib/src/sparql/dataset.rs
  3. 2
      lib/src/sparql/eval.rs
  4. 23
      lib/src/sparql/mod.rs
  5. 61
      lib/src/sparql/parser.rs
  6. 9
      lib/src/sparql/plan.rs
  7. 403
      lib/src/sparql/update.rs
  8. 37
      lib/src/store/memory.rs
  9. 7
      lib/src/store/numeric_encoder.rs
  10. 25
      lib/src/store/rocksdb.rs
  11. 23
      lib/src/store/sled.rs
  12. 1
      testsuite/Cargo.toml
  13. 8
      testsuite/src/files.rs
  14. 102
      testsuite/src/manifest.rs
  15. 7
      testsuite/src/parser_evaluator.rs
  16. 42
      testsuite/src/report.rs
  17. 58
      testsuite/src/sparql_evaluator.rs
  18. 12
      testsuite/src/vocab.rs
  19. 8
      testsuite/tests/sparql.rs

@ -1391,7 +1391,7 @@ impl fmt::Display for QueryVariants {
base_iri, base_iri,
} => { } => {
if let Some(base_iri) = base_iri { if let Some(base_iri) = base_iri {
writeln!(f, "BASE <{}>\n", base_iri)?; writeln!(f, "BASE <{}>", base_iri)?;
} }
write!(f, "{}", SparqlGraphRootPattern { algebra, dataset }) write!(f, "{}", SparqlGraphRootPattern { algebra, dataset })
} }
@ -1402,7 +1402,7 @@ impl fmt::Display for QueryVariants {
base_iri, base_iri,
} => { } => {
if let Some(base_iri) = base_iri { if let Some(base_iri) = base_iri {
writeln!(f, "BASE <{}>\n", base_iri)?; writeln!(f, "BASE <{}>", base_iri)?;
} }
write!(f, "CONSTRUCT {{ ")?; write!(f, "CONSTRUCT {{ ")?;
for triple in construct.iter() { for triple in construct.iter() {
@ -1424,7 +1424,7 @@ impl fmt::Display for QueryVariants {
base_iri, base_iri,
} => { } => {
if let Some(base_iri) = base_iri { if let Some(base_iri) = base_iri {
writeln!(f, "BASE <{}>\n", base_iri.as_str())?; writeln!(f, "BASE <{}>", base_iri.as_str())?;
} }
write!( write!(
f, f,
@ -1442,7 +1442,7 @@ impl fmt::Display for QueryVariants {
base_iri, base_iri,
} => { } => {
if let Some(base_iri) = base_iri { if let Some(base_iri) = base_iri {
writeln!(f, "BASE <{}>\n", base_iri)?; writeln!(f, "BASE <{}>", base_iri)?;
} }
write!( write!(
f, f,
@ -1467,10 +1467,10 @@ pub enum GraphUpdateOperation {
DeleteData { data: Vec<QuadPattern> }, DeleteData { data: Vec<QuadPattern> },
/// [delete insert](https://www.w3.org/TR/sparql11-update/#def_deleteinsertoperation) /// [delete insert](https://www.w3.org/TR/sparql11-update/#def_deleteinsertoperation)
DeleteInsert { DeleteInsert {
delete: Option<Vec<QuadPattern>>, delete: Vec<QuadPattern>,
insert: Option<Vec<QuadPattern>>, insert: Vec<QuadPattern>,
using: Rc<DatasetSpec>, using: DatasetSpec,
algebra: Rc<GraphPattern>, algebra: GraphPattern,
}, },
/// [load](https://www.w3.org/TR/sparql11-update/#def_loadoperation) /// [load](https://www.w3.org/TR/sparql11-update/#def_loadoperation)
Load { Load {
@ -1509,14 +1509,14 @@ impl fmt::Display for GraphUpdateOperation {
using, using,
algebra, algebra,
} => { } => {
if let Some(delete) = delete { if !delete.is_empty() {
writeln!(f, "DELETE {{")?; writeln!(f, "DELETE {{")?;
for quad in delete { for quad in delete {
writeln!(f, "\t{}", quad)?; writeln!(f, "\t{}", quad)?;
} }
writeln!(f, "}}")?; writeln!(f, "}}")?;
} }
if let Some(insert) = insert { if !insert.is_empty() {
writeln!(f, "INSERT {{")?; writeln!(f, "INSERT {{")?;
for quad in insert { for quad in insert {
writeln!(f, "\t{}", quad)?; writeln!(f, "\t{}", quad)?;

@ -235,10 +235,10 @@ fn try_map_quad_pattern<I: StrId>(
graph_name: Option<EncodedTerm<DatasetStrId<I>>>, graph_name: Option<EncodedTerm<DatasetStrId<I>>>,
) -> Option<QuadPattern<I>> { ) -> Option<QuadPattern<I>> {
Some(( Some((
transpose(subject.map(|t| t.try_map_id(unwrap_store_id)))?, transpose(subject.map(|t| t.try_map_id(unwrap_store_id).ok()))?,
transpose(predicate.map(|t| t.try_map_id(unwrap_store_id)))?, transpose(predicate.map(|t| t.try_map_id(unwrap_store_id).ok()))?,
transpose(object.map(|t| t.try_map_id(unwrap_store_id)))?, transpose(object.map(|t| t.try_map_id(unwrap_store_id).ok()))?,
transpose(graph_name.map(|t| t.try_map_id(unwrap_store_id)))?, transpose(graph_name.map(|t| t.try_map_id(unwrap_store_id).ok()))?,
)) ))
} }
@ -250,10 +250,10 @@ fn transpose<T>(o: Option<Option<T>>) -> Option<Option<T>> {
} }
} }
fn unwrap_store_id<I: StrId>(id: DatasetStrId<I>) -> Option<I> { fn unwrap_store_id<I: StrId>(id: DatasetStrId<I>) -> Result<I, ()> {
match id { match id {
DatasetStrId::Store(id) => Some(id), DatasetStrId::Store(id) => Ok(id),
DatasetStrId::Temporary(_) => None, DatasetStrId::Temporary(_) => Err(()),
} }
} }

@ -120,7 +120,7 @@ where
})) }))
} }
fn eval_plan( pub fn eval_plan(
&self, &self,
node: &PlanNode<S::StrId>, node: &PlanNode<S::StrId>,
from: EncodedTuple<S::StrId>, from: EncodedTuple<S::StrId>,

@ -11,6 +11,7 @@ mod model;
mod parser; mod parser;
mod plan; mod plan;
mod plan_builder; mod plan_builder;
mod update;
mod xml_results; mod xml_results;
use crate::model::{GraphName, NamedNode, NamedOrBlankNode}; use crate::model::{GraphName, NamedNode, NamedOrBlankNode};
@ -28,8 +29,9 @@ pub use crate::sparql::parser::ParseError;
pub use crate::sparql::parser::{Query, Update}; pub use crate::sparql::parser::{Query, Update};
use crate::sparql::plan::{PlanNode, TripleTemplate}; use crate::sparql::plan::{PlanNode, TripleTemplate};
use crate::sparql::plan_builder::PlanBuilder; use crate::sparql::plan_builder::PlanBuilder;
use crate::store::numeric_encoder::StrEncodingAware; use crate::sparql::update::SimpleUpdateEvaluator;
use crate::store::ReadableEncodedStore; use crate::store::numeric_encoder::{StrContainer, StrEncodingAware};
use crate::store::{ReadableEncodedStore, WritableEncodedStore};
use std::convert::TryInto; use std::convert::TryInto;
use std::error::Error; use std::error::Error;
use std::rc::Rc; use std::rc::Rc;
@ -302,3 +304,20 @@ impl<S: ServiceHandler> ServiceHandler for ErrorConversionServiceHandler<S> {
.map_err(EvaluationError::wrap) .map_err(EvaluationError::wrap)
} }
} }
pub(crate) fn evaluate_update<
R: ReadableEncodedStore + Clone + 'static,
W: StrContainer<StrId = R::StrId> + WritableEncodedStore<StrId = R::StrId>,
>(
read: R,
write: &mut W,
update: &Update,
) -> Result<(), EvaluationError> {
SimpleUpdateEvaluator::new(
read,
write,
update.base_iri.clone(),
Rc::new(EmptyServiceHandler),
)
.eval_all(&update.operations)
}

@ -100,11 +100,17 @@ impl<'a> TryFrom<&'a String> for Query {
/// # Result::Ok::<_, oxigraph::sparql::ParseError>(()) /// # Result::Ok::<_, oxigraph::sparql::ParseError>(())
/// ``` /// ```
#[derive(Eq, PartialEq, Debug, Clone, Hash)] #[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct Update(pub(crate) Vec<GraphUpdateOperation>); pub struct Update {
pub(crate) base_iri: Option<Rc<Iri<String>>>,
pub(crate) operations: Vec<GraphUpdateOperation>,
}
impl fmt::Display for Update { impl fmt::Display for Update {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for update in &self.0 { if let Some(base_iri) = &self.base_iri {
writeln!(f, "BASE <{}>", base_iri)?;
}
for update in &self.operations {
writeln!(f, "{} ;", update)?; writeln!(f, "{} ;", update)?;
} }
Ok(()) Ok(())
@ -130,13 +136,14 @@ impl Update {
aggregations: Vec::default(), aggregations: Vec::default(),
}; };
Ok(Self( let operations = parser::UpdateInit(&unescape_unicode_codepoints(update), &mut state)
parser::UpdateInit(&unescape_unicode_codepoints(update), &mut state).map_err(|e| { .map_err(|e| ParseError {
ParseError {
inner: ParseErrorKind::Parser(e), inner: ParseErrorKind::Parser(e),
} })?;
})?, Ok(Self {
)) operations,
base_iri: state.base_iri,
})
} }
} }
@ -474,20 +481,20 @@ fn copy_graph(
) )
.into()]); .into()]);
GraphUpdateOperation::DeleteInsert { GraphUpdateOperation::DeleteInsert {
delete: None, delete: Vec::new(),
insert: Some(vec![QuadPattern::new( insert: vec![QuadPattern::new(
Variable::new("s"), Variable::new("s"),
Variable::new("p"), Variable::new("p"),
Variable::new("o"), Variable::new("o"),
to.into(), to.into(),
)]), )],
using: Rc::new(DatasetSpec::default()), using: DatasetSpec::default(),
algebra: Rc::new(match &from { algebra: match from {
NamedOrDefaultGraphTarget::NamedNode(from) => { NamedOrDefaultGraphTarget::NamedNode(from) => {
GraphPattern::Graph(from.clone().into(), Box::new(bgp)) GraphPattern::Graph(from.into(), Box::new(bgp))
} }
NamedOrDefaultGraphTarget::DefaultGraph => bgp, NamedOrDefaultGraphTarget::DefaultGraph => bgp,
}), },
} }
} }
@ -1013,38 +1020,40 @@ parser! {
} }
}).fold(GraphPattern::BGP(Vec::new()), new_join); }).fold(GraphPattern::BGP(Vec::new()), new_join);
vec![GraphUpdateOperation::DeleteInsert { vec![GraphUpdateOperation::DeleteInsert {
delete: Some(d), delete: d,
insert: None, insert: Vec::new(),
using: Rc::new(DatasetSpec::default()), using: DatasetSpec::default(),
algebra: Rc::new(algebra) algebra
}] }]
} }
//[41] //[41]
rule Modify() -> Vec<GraphUpdateOperation> = with:Modify_with() _ c:Modify_clauses() _ using:(UsingClause() ** (_)) _ i("WHERE") _ algebra:GroupGraphPattern() { rule Modify() -> Vec<GraphUpdateOperation> = with:Modify_with() _ c:Modify_clauses() _ using:(UsingClause() ** (_)) _ i("WHERE") _ algebra:GroupGraphPattern() {
let (mut delete, mut insert) = c; let (delete, insert) = c;
let mut delete = delete.unwrap_or_else(Vec::new);
let mut insert = insert.unwrap_or_else(Vec::new);
let mut algebra = algebra; let mut algebra = algebra;
if let Some(with) = with { if let Some(with) = with {
// We inject WITH everywhere // We inject WITH everywhere
delete = delete.map(|quads| quads.into_iter().map(|q| if q.graph_name.is_none() { delete = delete.into_iter().map(|q| if q.graph_name.is_none() {
QuadPattern::new(q.subject, q.predicate, q.object, Some(with.clone().into())) QuadPattern::new(q.subject, q.predicate, q.object, Some(with.clone().into()))
} else { } else {
q q
}).collect()); }).collect();
insert = insert.map(|quads| quads.into_iter().map(|q| if q.graph_name.is_none() { insert = insert.into_iter().map(|q| if q.graph_name.is_none() {
QuadPattern::new(q.subject, q.predicate, q.object, Some(with.clone().into())) QuadPattern::new(q.subject, q.predicate, q.object, Some(with.clone().into()))
} else { } else {
q q
}).collect()); }).collect();
algebra = GraphPattern::Graph(with.into(), Box::new(algebra)); algebra = GraphPattern::Graph(with.into(), Box::new(algebra));
} }
vec![GraphUpdateOperation::DeleteInsert { vec![GraphUpdateOperation::DeleteInsert {
delete, delete,
insert, insert,
using: Rc::new(using.into_iter().fold(DatasetSpec::default(), |mut a, b| a + b)), using: using.into_iter().fold(DatasetSpec::default(), |mut a, b| a + b),
algebra: Rc::new(algebra) algebra
}] }]
} }
rule Modify_with() -> Option<NamedNode> = i("WITH") _ i:iri() _ { rule Modify_with() -> Option<NamedNode> = i("WITH") _ i:iri() _ {

@ -551,3 +551,12 @@ impl<I: StrId> EncodedTuple<I> {
} }
} }
} }
impl<I: StrId> IntoIterator for EncodedTuple<I> {
type Item = Option<EncodedTerm<I>>;
type IntoIter = std::vec::IntoIter<Option<EncodedTerm<I>>>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}

@ -0,0 +1,403 @@
use crate::sparql::algebra::{
DatasetSpec, GraphPattern, GraphTarget, GraphUpdateOperation, NamedNodeOrVariable, QuadPattern,
TermOrVariable,
};
use crate::sparql::dataset::{DatasetStrId, DatasetView};
use crate::sparql::eval::SimpleEvaluator;
use crate::sparql::plan::EncodedTuple;
use crate::sparql::plan_builder::PlanBuilder;
use crate::sparql::{EvaluationError, ServiceHandler, Variable};
use crate::store::numeric_encoder::{
EncodedQuad, EncodedTerm, ReadEncoder, StrContainer, StrLookup, WriteEncoder,
};
use crate::store::{ReadableEncodedStore, WritableEncodedStore};
use oxiri::Iri;
use std::rc::Rc;
pub(crate) struct SimpleUpdateEvaluator<'a, R, W> {
read: R,
write: &'a mut W,
base_iri: Option<Rc<Iri<String>>>,
service_handler: Rc<dyn ServiceHandler<Error = EvaluationError>>,
}
impl<
'a,
R: ReadableEncodedStore + Clone + 'static,
W: StrContainer<StrId = R::StrId> + WritableEncodedStore<StrId = R::StrId> + 'a,
> SimpleUpdateEvaluator<'a, R, W>
{
pub fn new(
read: R,
write: &'a mut W,
base_iri: Option<Rc<Iri<String>>>,
service_handler: Rc<dyn ServiceHandler<Error = EvaluationError>>,
) -> Self {
Self {
read,
write,
base_iri,
service_handler,
}
}
pub fn eval_all(&mut self, updates: &[GraphUpdateOperation]) -> Result<(), EvaluationError> {
for update in updates {
self.eval(update)?;
}
Ok(())
}
fn eval(&mut self, update: &GraphUpdateOperation) -> Result<(), EvaluationError> {
match update {
GraphUpdateOperation::InsertData { data } => self.eval_insert_data(data),
GraphUpdateOperation::DeleteData { data } => self.eval_delete_data(data),
GraphUpdateOperation::DeleteInsert {
delete,
insert,
using,
algebra,
} => self.eval_delete_insert(delete, insert, using, algebra),
GraphUpdateOperation::Load { .. } => Err(EvaluationError::msg(
"SPARQL UPDATE LOAD operation is not implemented yet",
)),
GraphUpdateOperation::Clear { graph, .. } => self.eval_clear(graph),
GraphUpdateOperation::Create { .. } => Ok(()),
GraphUpdateOperation::Drop { graph, .. } => self.eval_clear(graph),
}
}
fn eval_insert_data(&mut self, data: &[QuadPattern]) -> Result<(), EvaluationError> {
for quad in data {
if let Some(quad) = self.encode_quad_for_insertion(quad, &[], &[])? {
self.write.insert_encoded(&quad).map_err(to_eval_error)?;
}
}
Ok(())
}
fn eval_delete_data(&mut self, data: &[QuadPattern]) -> Result<(), EvaluationError> {
for quad in data {
if let Some(quad) = self.encode_quad_for_deletion(quad, &[], &[])? {
self.write.remove_encoded(&quad).map_err(to_eval_error)?;
}
}
Ok(())
}
fn eval_delete_insert(
&mut self,
delete: &[QuadPattern],
insert: &[QuadPattern],
using: &DatasetSpec,
algebra: &GraphPattern,
) -> Result<(), EvaluationError> {
let dataset = Rc::new(DatasetView::new(self.read.clone(), false, &[], &[], using)?);
let (plan, variables) = PlanBuilder::build(dataset.as_ref(), algebra)?;
let evaluator = SimpleEvaluator::<DatasetView<R>>::new(
dataset.clone(),
self.base_iri.clone(),
self.service_handler.clone(),
);
for tuple in evaluator.eval_plan(&plan, EncodedTuple::with_capacity(variables.len())) {
// We map the tuple to only get store strings
let tuple = tuple?
.into_iter()
.map(|t| {
Ok(if let Some(t) = t {
Some(
t.try_map_id(|id| {
if let DatasetStrId::Store(s) = id {
Ok(s)
} else {
self.write
.insert_str(
&dataset
.get_str(id)
.map_err(to_eval_error)?
.ok_or_else(|| {
EvaluationError::msg(
"String not stored in the string store",
)
})
.map_err(to_eval_error)?,
)
.map_err(to_eval_error)
}
})
.map_err(to_eval_error)?,
)
} else {
None
})
})
.collect::<Result<Vec<_>, EvaluationError>>()?;
for quad in delete {
if let Some(quad) = self.encode_quad_for_deletion(quad, &variables, &tuple)? {
self.write.remove_encoded(&quad).map_err(to_eval_error)?;
}
}
for quad in insert {
if let Some(quad) = self.encode_quad_for_insertion(quad, &variables, &tuple)? {
self.write.insert_encoded(&quad).map_err(to_eval_error)?;
}
}
}
Ok(())
}
fn eval_clear(&mut self, graph: &GraphTarget) -> Result<(), EvaluationError> {
match graph {
GraphTarget::NamedNode(graph) => {
if let Some(graph) = self
.read
.get_encoded_named_node(graph.into())
.map_err(to_eval_error)?
{
for quad in self
.read
.encoded_quads_for_pattern(None, None, None, Some(graph))
{
self.write
.remove_encoded(&quad.map_err(to_eval_error)?)
.map_err(to_eval_error)?;
}
} else {
//we do not track created graph so it's fine
}
}
GraphTarget::DefaultGraph => {
for quad in self.read.encoded_quads_for_pattern(
None,
None,
None,
Some(EncodedTerm::DefaultGraph),
) {
self.write
.remove_encoded(&quad.map_err(to_eval_error)?)
.map_err(to_eval_error)?;
}
}
GraphTarget::NamedGraphs => {
for quad in self.read.encoded_quads_for_pattern(None, None, None, None) {
let quad = quad.map_err(to_eval_error)?;
if !quad.graph_name.is_default_graph() {
self.write.remove_encoded(&quad).map_err(to_eval_error)?;
}
}
}
GraphTarget::AllGraphs => {
for quad in self.read.encoded_quads_for_pattern(None, None, None, None) {
self.write
.remove_encoded(&quad.map_err(to_eval_error)?)
.map_err(to_eval_error)?;
}
}
};
Ok(())
}
fn encode_quad_for_insertion(
&mut self,
quad: &QuadPattern,
variables: &[Variable],
values: &[Option<EncodedTerm<R::StrId>>],
) -> Result<Option<EncodedQuad<R::StrId>>, EvaluationError> {
Ok(Some(EncodedQuad {
subject: if let Some(subject) =
self.encode_term_for_insertion(&quad.subject, variables, values, |t| {
t.is_named_node() || t.is_blank_node()
})? {
subject
} else {
return Ok(None);
},
predicate: if let Some(predicate) =
self.encode_named_node_for_insertion(&quad.predicate, variables, values)?
{
predicate
} else {
return Ok(None);
},
object: if let Some(object) =
self.encode_term_for_insertion(&quad.object, variables, values, |t| {
!t.is_default_graph()
})? {
object
} else {
return Ok(None);
},
graph_name: if let Some(graph_name) = &quad.graph_name {
if let Some(graph_name) =
self.encode_named_node_for_insertion(graph_name, variables, values)?
{
graph_name
} else {
return Ok(None);
}
} else {
EncodedTerm::DefaultGraph
},
}))
}
fn encode_term_for_insertion(
&mut self,
term: &TermOrVariable,
variables: &[Variable],
values: &[Option<EncodedTerm<R::StrId>>],
validate: impl FnOnce(&EncodedTerm<R::StrId>) -> bool,
) -> Result<Option<EncodedTerm<R::StrId>>, EvaluationError> {
Ok(match term {
TermOrVariable::Term(term) => {
Some(self.write.encode_term(term.into()).map_err(to_eval_error)?)
}
TermOrVariable::Variable(v) => {
if let Some(Some(term)) = variables
.iter()
.position(|v2| v == v2)
.and_then(|i| values.get(i))
{
if validate(term) {
Some(*term)
} else {
None
}
} else {
None
}
}
})
}
fn encode_named_node_for_insertion(
&mut self,
term: &NamedNodeOrVariable,
variables: &[Variable],
values: &[Option<EncodedTerm<R::StrId>>],
) -> Result<Option<EncodedTerm<R::StrId>>, EvaluationError> {
Ok(match term {
NamedNodeOrVariable::NamedNode(term) => Some(
self.write
.encode_named_node(term.into())
.map_err(to_eval_error)?,
),
NamedNodeOrVariable::Variable(v) => {
if let Some(Some(term)) = variables
.iter()
.position(|v2| v == v2)
.and_then(|i| values.get(i))
{
if term.is_named_node() {
Some(*term)
} else {
None
}
} else {
None
}
}
})
}
fn encode_quad_for_deletion(
&self,
quad: &QuadPattern,
variables: &[Variable],
values: &[Option<EncodedTerm<R::StrId>>],
) -> Result<Option<EncodedQuad<R::StrId>>, EvaluationError> {
Ok(Some(EncodedQuad {
subject: if let Some(subject) =
self.encode_term_for_deletion(&quad.subject, variables, values)?
{
subject
} else {
return Ok(None);
},
predicate: if let Some(predicate) =
self.encode_named_node_for_deletion(&quad.predicate, variables, values)?
{
predicate
} else {
return Ok(None);
},
object: if let Some(object) =
self.encode_term_for_deletion(&quad.object, variables, values)?
{
object
} else {
return Ok(None);
},
graph_name: if let Some(graph_name) = &quad.graph_name {
if let Some(graph_name) =
self.encode_named_node_for_deletion(graph_name, variables, values)?
{
graph_name
} else {
return Ok(None);
}
} else {
EncodedTerm::DefaultGraph
},
}))
}
fn encode_term_for_deletion(
&self,
term: &TermOrVariable,
variables: &[Variable],
values: &[Option<EncodedTerm<R::StrId>>],
) -> Result<Option<EncodedTerm<R::StrId>>, EvaluationError> {
Ok(match term {
TermOrVariable::Term(term) => self
.read
.get_encoded_term(term.into())
.map_err(to_eval_error)?,
TermOrVariable::Variable(v) => {
if let Some(Some(term)) = variables
.iter()
.position(|v2| v == v2)
.and_then(|i| values.get(i))
{
Some(*term)
} else {
None
}
}
})
}
fn encode_named_node_for_deletion(
&self,
term: &NamedNodeOrVariable,
variables: &[Variable],
values: &[Option<EncodedTerm<R::StrId>>],
) -> Result<Option<EncodedTerm<R::StrId>>, EvaluationError> {
Ok(match term {
NamedNodeOrVariable::NamedNode(term) => self
.read
.get_encoded_named_node(term.into())
.map_err(to_eval_error)?,
NamedNodeOrVariable::Variable(v) => {
if let Some(Some(term)) = variables
.iter()
.position(|v2| v == v2)
.and_then(|i| values.get(i))
{
if term.is_named_node() {
Some(*term)
} else {
None
}
} else {
None
}
}
})
}
}
fn to_eval_error(e: impl Into<EvaluationError>) -> EvaluationError {
e.into()
}

@ -3,7 +3,10 @@
use crate::error::{invalid_input_error, UnwrapInfallible}; use crate::error::{invalid_input_error, UnwrapInfallible};
use crate::io::{DatasetFormat, DatasetParser, GraphFormat, GraphParser}; use crate::io::{DatasetFormat, DatasetParser, GraphFormat, GraphParser};
use crate::model::*; use crate::model::*;
use crate::sparql::{EvaluationError, Query, QueryOptions, QueryResults, SimplePreparedQuery}; use crate::sparql::{
evaluate_update, EvaluationError, Query, QueryOptions, QueryResults, SimplePreparedQuery,
Update,
};
use crate::store::numeric_encoder::{ use crate::store::numeric_encoder::{
Decoder, ReadEncoder, StrContainer, StrEncodingAware, StrId, StrLookup, WriteEncoder, Decoder, ReadEncoder, StrContainer, StrEncodingAware, StrId, StrLookup, WriteEncoder,
}; };
@ -233,6 +236,38 @@ impl MemoryStore {
indexes.default_spo.is_empty() && indexes.spog.is_empty() indexes.default_spo.is_empty() && indexes.spog.is_empty()
} }
/// Executes a [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/).
///
/// The [`LOAD` operation](https://www.w3.org/TR/sparql11-update/#load) is not supported yet.
/// The store does not track the existence of empty named graphs.
/// This method has no ACID guarantees.
///
/// Usage example:
/// ```
/// use oxigraph::MemoryStore;
/// use oxigraph::model::*;
///
/// let store = MemoryStore::new();
///
/// // insertion
/// store.update("INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }")?;
///
/// // we inspect the store contents
/// let ex = NamedNodeRef::new("http://example.com").unwrap();
/// assert!(store.contains(QuadRef::new(ex, ex, ex, None)));
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
pub fn update(
&self,
update: impl TryInto<Update, Error = impl Into<EvaluationError>>,
) -> Result<(), EvaluationError> {
evaluate_update(
self.clone(),
&mut &*self,
&update.try_into().map_err(|e| e.into())?,
)
}
/// Executes an ACID transaction. /// Executes an ACID transaction.
/// ///
/// The transaction is executed if the given closure returns `Ok`. /// The transaction is executed if the given closure returns `Ok`.

@ -379,8 +379,11 @@ impl<I: StrId> EncodedTerm<I> {
} }
} }
pub fn try_map_id<J: StrId>(self, mapping: impl Fn(I) -> Option<J>) -> Option<EncodedTerm<J>> { pub fn try_map_id<J: StrId, E>(
Some(match self { self,
mut mapping: impl FnMut(I) -> Result<J, E>,
) -> Result<EncodedTerm<J>, E> {
Ok(match self {
Self::DefaultGraph { .. } => EncodedTerm::DefaultGraph, Self::DefaultGraph { .. } => EncodedTerm::DefaultGraph,
Self::NamedNode { iri_id } => EncodedTerm::NamedNode { Self::NamedNode { iri_id } => EncodedTerm::NamedNode {
iri_id: mapping(iri_id)?, iri_id: mapping(iri_id)?,

@ -3,7 +3,10 @@
use crate::error::invalid_data_error; use crate::error::invalid_data_error;
use crate::io::{DatasetFormat, GraphFormat}; use crate::io::{DatasetFormat, GraphFormat};
use crate::model::*; use crate::model::*;
use crate::sparql::{EvaluationError, Query, QueryOptions, QueryResults, SimplePreparedQuery}; use crate::sparql::{
evaluate_update, EvaluationError, Query, QueryOptions, QueryResults, SimplePreparedQuery,
Update,
};
use crate::store::binary_encoder::*; use crate::store::binary_encoder::*;
use crate::store::numeric_encoder::{ use crate::store::numeric_encoder::{
Decoder, ReadEncoder, StrContainer, StrEncodingAware, StrLookup, WriteEncoder, Decoder, ReadEncoder, StrContainer, StrEncodingAware, StrLookup, WriteEncoder,
@ -212,6 +215,26 @@ impl RocksDbStore {
default && named default && named
} }
/// Executes a [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/).
///
/// The [`LOAD` operation](https://www.w3.org/TR/sparql11-update/#load) is not supported yet.
/// The store does not track the existence of empty named graphs.
/// This method has no ACID guarantees.
///
/// See [`MemoryStore`](../memory/struct.MemoryStore.html#method.update) for a usage example.
pub fn update(
&self,
update: impl TryInto<Update, Error = impl Into<EvaluationError>>,
) -> Result<(), EvaluationError> {
let mut writer = self.auto_batch_writer();
evaluate_update(
self.clone(),
&mut writer,
&update.try_into().map_err(|e| e.into())?,
)?;
Ok(writer.apply()?)
}
/// Executes an ACID transaction. /// Executes an ACID transaction.
/// ///
/// The transaction is executed if the given closure returns `Ok`. /// The transaction is executed if the given closure returns `Ok`.

@ -3,7 +3,10 @@
use crate::error::invalid_data_error; use crate::error::invalid_data_error;
use crate::io::{DatasetFormat, GraphFormat}; use crate::io::{DatasetFormat, GraphFormat};
use crate::model::*; use crate::model::*;
use crate::sparql::{EvaluationError, Query, QueryOptions, QueryResults, SimplePreparedQuery}; use crate::sparql::{
evaluate_update, EvaluationError, Query, QueryOptions, QueryResults, SimplePreparedQuery,
Update,
};
use crate::store::binary_encoder::*; use crate::store::binary_encoder::*;
use crate::store::numeric_encoder::{ use crate::store::numeric_encoder::{
Decoder, ReadEncoder, StrContainer, StrEncodingAware, StrLookup, WriteEncoder, Decoder, ReadEncoder, StrContainer, StrEncodingAware, StrLookup, WriteEncoder,
@ -203,6 +206,24 @@ impl SledStore {
self.gspo.is_empty() && self.dspo.is_empty() self.gspo.is_empty() && self.dspo.is_empty()
} }
/// Executes a [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/).
///
/// The [`LOAD` operation](https://www.w3.org/TR/sparql11-update/#load) is not supported yet.
/// The store does not track the existence of empty named graphs.
/// This method has no ACID guarantees.
///
/// See [`MemoryStore`](../memory/struct.MemoryStore.html#method.update) for a usage example.
pub fn update(
&self,
update: impl TryInto<Update, Error = impl Into<EvaluationError>>,
) -> Result<(), EvaluationError> {
evaluate_update(
self.clone(),
&mut &*self,
&update.try_into().map_err(|e| e.into())?,
)
}
/// Executes an ACID transaction. /// Executes an ACID transaction.
/// ///
/// The transaction is executed if the given closure returns `Ok`. /// The transaction is executed if the given closure returns `Ok`.

@ -15,6 +15,7 @@ publish = false
anyhow = "1" anyhow = "1"
chrono = "0.4" chrono = "0.4"
oxigraph = { version = "0.1", path="../lib" } oxigraph = { version = "0.1", path="../lib" }
text-diff = "0.4"
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"

@ -1,6 +1,6 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use oxigraph::io::{DatasetFormat, GraphFormat}; use oxigraph::io::{DatasetFormat, GraphFormat};
use oxigraph::model::GraphName; use oxigraph::model::{GraphName, GraphNameRef};
use oxigraph::MemoryStore; use oxigraph::MemoryStore;
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader, Read}; use std::io::{BufRead, BufReader, Read};
@ -39,7 +39,11 @@ pub fn read_file_to_string(url: &str) -> Result<String> {
Ok(buf) Ok(buf)
} }
pub fn load_to_store(url: &str, store: &MemoryStore, to_graph_name: &GraphName) -> Result<()> { pub fn load_to_store<'a>(
url: &str,
store: &MemoryStore,
to_graph_name: impl Into<GraphNameRef<'a>>,
) -> Result<()> {
if url.ends_with(".nt") { if url.ends_with(".nt") {
store.load_graph( store.load_graph(
read_file(url)?, read_file(url)?,

@ -13,10 +13,12 @@ pub struct Test {
pub comment: Option<String>, pub comment: Option<String>,
pub action: Option<String>, pub action: Option<String>,
pub query: Option<String>, pub query: Option<String>,
pub update: Option<String>,
pub data: Option<String>, pub data: Option<String>,
pub graph_data: Vec<String>, pub graph_data: Vec<(NamedNode, String)>,
pub service_data: Vec<(String, String)>, pub service_data: Vec<(String, String)>,
pub result: Option<String>, pub result: Option<String>,
pub result_graph_data: Vec<(NamedNode, String)>,
} }
impl fmt::Display for Test { impl fmt::Display for Test {
@ -37,7 +39,7 @@ impl fmt::Display for Test {
for data in &self.data { for data in &self.data {
write!(f, " with data {}", data)?; write!(f, " with data {}", data)?;
} }
for data in &self.graph_data { for (_, data) in &self.graph_data {
write!(f, " and graph data {}", data)?; write!(f, " and graph data {}", data)?;
} }
for result in &self.result { for result in &self.result {
@ -85,10 +87,10 @@ impl Iterator for TestManifest {
Some(Term::Literal(c)) => Some(c.value().to_string()), Some(Term::Literal(c)) => Some(c.value().to_string()),
_ => None, _ => None,
}; };
let (action, query, data, graph_data, service_data) = let (action, query, update, data, graph_data, service_data) =
match object_for_subject_predicate(&self.graph, &test_node, mf::ACTION) { match object_for_subject_predicate(&self.graph, &test_node, mf::ACTION) {
Some(Term::NamedNode(n)) => { Some(Term::NamedNode(n)) => {
(Some(n.into_string()), None, None, vec![], vec![]) (Some(n.into_string()), None, None, None, vec![], vec![])
} }
Some(Term::BlankNode(n)) => { Some(Term::BlankNode(n)) => {
let query = let query =
@ -96,15 +98,52 @@ impl Iterator for TestManifest {
Some(Term::NamedNode(q)) => Some(q.into_string()), Some(Term::NamedNode(q)) => Some(q.into_string()),
_ => None, _ => None,
}; };
let update =
match object_for_subject_predicate(&self.graph, &n, ut::REQUEST) {
Some(Term::NamedNode(q)) => Some(q.into_string()),
_ => None,
};
let data = match object_for_subject_predicate(&self.graph, &n, qt::DATA) let data = match object_for_subject_predicate(&self.graph, &n, qt::DATA)
.or_else(|| object_for_subject_predicate(&self.graph, &n, ut::DATA))
{ {
Some(Term::NamedNode(q)) => Some(q.into_string()), Some(Term::NamedNode(q)) => Some(q.into_string()),
_ => None, _ => None,
}; };
let graph_data = let graph_data =
objects_for_subject_predicate(&self.graph, &n, qt::GRAPH_DATA) objects_for_subject_predicate(&self.graph, &n, qt::GRAPH_DATA)
.chain(objects_for_subject_predicate(
&self.graph,
&n,
ut::GRAPH_DATA,
))
.filter_map(|g| match g { .filter_map(|g| match g {
Term::NamedNode(q) => Some(q.into_string()), Term::NamedNode(q) => Some((q.clone(), q.into_string())),
Term::BlankNode(node) => {
if let Some(Term::NamedNode(graph)) =
object_for_subject_predicate(
&self.graph,
&node,
ut::GRAPH,
)
{
if let Some(Term::Literal(name)) =
object_for_subject_predicate(
&self.graph,
&node,
rdfs::LABEL,
)
{
Some((
NamedNode::new(name.value()).unwrap(),
graph.into_string(),
))
} else {
Some((graph.clone(), graph.into_string()))
}
} else {
None
}
}
_ => None, _ => None,
}) })
.collect(); .collect();
@ -133,18 +172,59 @@ impl Iterator for TestManifest {
} }
}) })
.collect(); .collect();
(None, query, data, graph_data, service_data) (None, query, update, data, graph_data, service_data)
} }
Some(_) => return Some(Err(anyhow!("invalid action"))), Some(_) => return Some(Err(anyhow!("invalid action"))),
None => { None => {
return Some(Err(anyhow!("action not found for test {}", test_node))); return Some(Err(anyhow!("action not found for test {}", test_node)));
} }
}; };
let result = match object_for_subject_predicate(&self.graph, &test_node, mf::RESULT) let (result, result_graph_data) =
match object_for_subject_predicate(&self.graph, &test_node, mf::RESULT) {
Some(Term::NamedNode(n)) => (Some(n.into_string()), Vec::new()),
Some(Term::BlankNode(n)) => (
if let Some(Term::NamedNode(result)) =
object_for_subject_predicate(&self.graph, &n, ut::DATA)
{ {
Some(Term::NamedNode(n)) => Some(n.into_string()), Some(result.into_string())
} else {
None
},
objects_for_subject_predicate(&self.graph, &n, ut::GRAPH_DATA)
.filter_map(|g| match g {
Term::NamedNode(q) => Some((q.clone(), q.into_string())),
Term::BlankNode(node) => {
if let Some(Term::NamedNode(graph)) =
object_for_subject_predicate(
&self.graph,
&node,
ut::GRAPH,
)
{
if let Some(Term::Literal(name)) =
object_for_subject_predicate(
&self.graph,
&node,
rdfs::LABEL,
)
{
Some((
NamedNode::new(name.value()).unwrap(),
graph.into_string(),
))
} else {
Some((graph.clone(), graph.into_string()))
}
} else {
None
}
}
_ => None,
})
.collect(),
),
Some(_) => return Some(Err(anyhow!("invalid result"))), Some(_) => return Some(Err(anyhow!("invalid result"))),
None => None, None => (None, Vec::new()),
}; };
Some(Ok(Test { Some(Ok(Test {
id: test_node, id: test_node,
@ -153,10 +233,12 @@ impl Iterator for TestManifest {
comment, comment,
action, action,
query, query,
update,
data, data,
graph_data, graph_data,
service_data, service_data,
result, result,
result_graph_data,
})) }))
} }
Some(_) => self.next(), Some(_) => self.next(),
@ -166,7 +248,7 @@ impl Iterator for TestManifest {
let manifest = let manifest =
NamedOrBlankNodeRef::from(NamedNodeRef::new(url.as_str()).unwrap()); NamedOrBlankNodeRef::from(NamedNodeRef::new(url.as_str()).unwrap());
if let Err(error) = if let Err(error) =
load_to_store(&url, &self.graph, &&GraphName::DefaultGraph) load_to_store(&url, &self.graph, GraphNameRef::DefaultGraph)
{ {
return Some(Err(error)); return Some(Err(error));
} }

@ -1,6 +1,6 @@
use crate::files::load_store; use crate::files::load_store;
use crate::manifest::Test; use crate::manifest::Test;
use crate::report::TestResult; use crate::report::{store_diff, TestResult};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use chrono::Utc; use chrono::Utc;
@ -59,9 +59,8 @@ fn evaluate_parser_test(test: &Test) -> Result<()> {
Ok(()) Ok(())
} else { } else {
Err(anyhow!( Err(anyhow!(
"The two files are not isomorphic. Expected:\n{}\nActual:\n{}", "The two files are not isomorphic. Diff:\n{}",
expected_graph, store_diff(&expected_graph, &actual_graph)
actual_graph
)) ))
} }
} }

@ -1,6 +1,8 @@
use anyhow::Result; use anyhow::Result;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use oxigraph::model::NamedNode; use oxigraph::model::NamedNode;
use oxigraph::MemoryStore;
use text_diff::{diff, Difference};
#[derive(Debug)] #[derive(Debug)]
pub struct TestResult { pub struct TestResult {
@ -8,3 +10,43 @@ pub struct TestResult {
pub outcome: Result<()>, pub outcome: Result<()>,
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
} }
pub fn store_diff(expected: &MemoryStore, actual: &MemoryStore) -> String {
let (_, changeset) = diff(
&normalize_store_text(expected),
&normalize_store_text(actual),
"\n",
);
let mut ret = String::new();
ret.push_str("Note: missing quads in yellow and extra quads in blue\n");
for seq in changeset {
match seq {
Difference::Same(x) => {
ret.push_str(&x);
ret.push('\n');
}
Difference::Add(x) => {
ret.push_str("\x1B[94m");
ret.push_str(&x);
ret.push_str("\x1B[0m");
ret.push('\n');
}
Difference::Rem(x) => {
ret.push_str("\x1B[93m");
ret.push_str(&x);
ret.push_str("\x1B[0m");
ret.push('\n');
}
}
}
ret
}
fn normalize_store_text(store: &MemoryStore) -> String {
let mut quads: Vec<_> = store
.quads_for_pattern(None, None, None, None)
.map(|q| q.to_string())
.collect();
quads.sort();
quads.join("\n")
}

@ -1,6 +1,6 @@
use crate::files::*; use crate::files::*;
use crate::manifest::*; use crate::manifest::*;
use crate::report::*; use crate::report::{store_diff, TestResult};
use crate::vocab::*; use crate::vocab::*;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use chrono::Utc; use chrono::Utc;
@ -72,10 +72,10 @@ fn evaluate_sparql_test(test: &Test) -> Result<()> {
{ {
let store = MemoryStore::new(); let store = MemoryStore::new();
if let Some(data) = &test.data { if let Some(data) = &test.data {
load_to_store(data, &store, &GraphName::DefaultGraph)?; load_to_store(data, &store, GraphNameRef::DefaultGraph)?;
} }
for graph_data in &test.graph_data { for (name, value) in &test.graph_data {
load_to_store(&graph_data, &store, &NamedNode::new(graph_data)?.into())?; load_to_store(value, &store, name)?;
} }
let query_file = test let query_file = test
.query .query
@ -157,6 +157,56 @@ fn evaluate_sparql_test(test: &Test) -> Result<()> {
)), )),
Err(_) => Ok(()), Err(_) => Ok(()),
} }
} else if test.kind
== "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#UpdateEvaluationTest"
{
let store = MemoryStore::new();
if let Some(data) = &test.data {
load_to_store(data, &store, &GraphName::DefaultGraph)?;
}
for (name, value) in &test.graph_data {
load_to_store(value, &store, name)?;
}
let result_store = MemoryStore::new();
if let Some(data) = &test.result {
load_to_store(data, &result_store, &GraphName::DefaultGraph)?;
}
for (name, value) in &test.result_graph_data {
load_to_store(value, &result_store, name)?;
}
let update_file = test
.update
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
match Update::parse(&read_file_to_string(update_file)?, Some(update_file)) {
Err(error) => Err(anyhow!(
"Failure to parse update of {} with error: {}",
test,
error
)),
Ok(update) => match store.update(update) {
Err(error) => Err(anyhow!(
"Failure to execute update of {} with error: {}",
test,
error
)),
Ok(()) => {
if store.is_isomorphic(&result_store) {
Ok(())
} else {
Err(anyhow!(
"Failure on {}.\nDiff:\n{}\nParsed update:\n{}\n",
test,
store_diff(&result_store, &store),
Update::parse(&read_file_to_string(update_file)?, Some(update_file))
.unwrap(),
))
}
}
},
}
} else { } else {
Err(anyhow!("Unsupported test type: {}", test.kind)) Err(anyhow!("Unsupported test type: {}", test.kind))
} }

@ -62,3 +62,15 @@ pub mod qt {
"http://www.w3.org/2001/sw/DataAccess/tests/test-query#endpoint", "http://www.w3.org/2001/sw/DataAccess/tests/test-query#endpoint",
); );
} }
pub mod ut {
use oxigraph::model::NamedNodeRef;
pub const DATA: NamedNodeRef<'_> =
NamedNodeRef::new_unchecked("http://www.w3.org/2009/sparql/tests/test-update#data");
pub const GRAPH_DATA: NamedNodeRef<'_> =
NamedNodeRef::new_unchecked("http://www.w3.org/2009/sparql/tests/test-update#graphData");
pub const GRAPH: NamedNodeRef<'_> =
NamedNodeRef::new_unchecked("http://www.w3.org/2009/sparql/tests/test-update#graph");
pub const REQUEST: NamedNodeRef<'_> =
NamedNodeRef::new_unchecked("http://www.w3.org/2009/sparql/tests/test-update#request");
}

@ -124,7 +124,11 @@ fn sparql11_federation_w3c_evaluation_testsuite() -> Result<()> {
#[test] #[test]
fn sparql11_update_w3c_evaluation_testsuite() -> Result<()> { fn sparql11_update_w3c_evaluation_testsuite() -> Result<()> {
run_testsuite( run_testsuite(
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-update-1/manifest.ttl", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/manifest-sparql11-update.ttl",
vec![], vec![
// LOAD is not implemented yet
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/update-silent/manifest#load-into-silent",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/update-silent/manifest#load-silent"
]
) )
} }

Loading…
Cancel
Save