From e6e83ff3696dde13ed98677f2f6d8bd3a8646e42 Mon Sep 17 00:00:00 2001 From: Tpt Date: Sat, 24 Apr 2021 12:27:42 +0200 Subject: [PATCH] Makes SPARQL algebra more strict Removes some invalid but encodable states --- lib/src/sparql/plan_builder.rs | 38 ++--- lib/src/sparql/update.rs | 262 ++++++++++++++------------------- lib/src/store.rs | 8 +- spargebra/src/algebra.rs | 76 ++++++++-- spargebra/src/parser.rs | 182 +++++++++++++++-------- spargebra/src/term.rs | 205 ++++++++++++++++++++++---- spargebra/src/update.rs | 41 ++++-- 7 files changed, 523 insertions(+), 289 deletions(-) diff --git a/lib/src/sparql/plan_builder.rs b/lib/src/sparql/plan_builder.rs index 873db541..2608ca23 100644 --- a/lib/src/sparql/plan_builder.rs +++ b/lib/src/sparql/plan_builder.rs @@ -775,7 +775,8 @@ impl<'a> PlanBuilder<'a> { TermOrVariable::Variable(variable) => { PatternValue::Variable(variable_key(variables, variable)) } - TermOrVariable::Term(Term::BlankNode(bnode)) => { + TermOrVariable::NamedNode(node) => PatternValue::Constant(self.build_named_node(node)?), + TermOrVariable::BlankNode(bnode) => { PatternValue::Variable(variable_key( variables, &Variable { @@ -784,7 +785,9 @@ impl<'a> PlanBuilder<'a> { )) //TODO: very bad hack to convert bnode to variable } - TermOrVariable::Term(term) => PatternValue::Constant(self.build_term(term)?), + TermOrVariable::Literal(literal) => { + PatternValue::Constant(self.build_literal(literal)?) + } }) } @@ -806,7 +809,7 @@ impl<'a> PlanBuilder<'a> { fn encode_bindings( &mut self, table_variables: &[Variable], - rows: &[Vec>], + rows: &[Vec>], variables: &mut Vec, ) -> Result, EvaluationError> { let bindings_variables_keys = table_variables @@ -821,8 +824,8 @@ impl<'a> PlanBuilder<'a> { result.set( bindings_variables_keys[key], match term { - NamedNodeOrLiteral::NamedNode(node) => self.build_named_node(node), - NamedNodeOrLiteral::Literal(literal) => self.build_literal(literal), + GroundTerm::NamedNode(node) => self.build_named_node(node), + GroundTerm::Literal(literal) => self.build_literal(literal), }?, ); } @@ -926,10 +929,15 @@ impl<'a> PlanBuilder<'a> { TermOrVariable::Variable(variable) => { TripleTemplateValue::Variable(variable_key(variables, variable)) } - TermOrVariable::Term(Term::BlankNode(bnode)) => { + TermOrVariable::NamedNode(node) => { + TripleTemplateValue::Constant(self.build_named_node(node)?) + } + TermOrVariable::BlankNode(bnode) => { TripleTemplateValue::BlankNode(bnode_key(bnodes, bnode)) } - TermOrVariable::Term(term) => TripleTemplateValue::Constant(self.build_term(term)?), + TermOrVariable::Literal(literal) => { + TripleTemplateValue::Constant(self.build_literal(literal)?) + } }) } @@ -1064,14 +1072,6 @@ impl<'a> PlanBuilder<'a> { ), }) } - - fn build_term(&mut self, term: &Term) -> Result { - match term { - Term::NamedNode(node) => self.build_named_node(node), - Term::BlankNode(_) => Err(EvaluationError::msg("Unexpected blank node")), - Term::Literal(literal) => self.build_literal(literal), - } - } } fn variable_key(variables: &mut Vec, variable: &Variable) -> usize { @@ -1130,7 +1130,7 @@ fn count_pattern_binds( if !assigned_variables.contains(v) { count -= 4; } - } else if let TermOrVariable::Term(Term::BlankNode(bnode)) = &pattern.subject { + } else if let TermOrVariable::BlankNode(bnode) = &pattern.subject { if !assigned_blank_nodes.contains(bnode) { count -= 4; } @@ -1148,7 +1148,7 @@ fn count_pattern_binds( if !assigned_variables.contains(v) { count -= 4; } - } else if let TermOrVariable::Term(Term::BlankNode(bnode)) = &pattern.object { + } else if let TermOrVariable::BlankNode(bnode) = &pattern.object { if !assigned_blank_nodes.contains(bnode) { count -= 4; } @@ -1165,7 +1165,7 @@ fn add_pattern_variables<'a>( ) { if let TermOrVariable::Variable(v) = &pattern.subject { variables.insert(v); - } else if let TermOrVariable::Term(Term::BlankNode(bnode)) = &pattern.subject { + } else if let TermOrVariable::BlankNode(bnode) = &pattern.subject { blank_nodes.insert(bnode); } if let NamedNodeOrVariable::Variable(v) = &pattern.predicate { @@ -1173,7 +1173,7 @@ fn add_pattern_variables<'a>( } if let TermOrVariable::Variable(v) = &pattern.object { variables.insert(v); - } else if let TermOrVariable::Term(Term::BlankNode(bnode)) = &pattern.object { + } else if let TermOrVariable::BlankNode(bnode) = &pattern.object { blank_nodes.insert(bnode); } } diff --git a/lib/src/sparql/update.rs b/lib/src/sparql/update.rs index 2f25aecb..7e07c96a 100644 --- a/lib/src/sparql/update.rs +++ b/lib/src/sparql/update.rs @@ -16,10 +16,11 @@ use crate::storage::Storage; use http::header::{ACCEPT, CONTENT_TYPE, USER_AGENT}; use http::{Method, Request, StatusCode}; use oxiri::Iri; -use spargebra::algebra::{GraphPattern, GraphTarget, QuadPattern}; +use spargebra::algebra::{GraphPattern, GraphTarget, GroundQuadPattern, QuadPattern}; use spargebra::term::{ - BlankNode, GraphName, Literal, NamedNode, NamedNodeOrVariable, NamedOrBlankNode, Quad, Term, - TermOrVariable, Variable, + BlankNode, GraphName, GraphNameOrVariable, GroundQuad, GroundTerm, GroundTermOrVariable, + Literal, NamedNode, NamedNodeOrVariable, NamedOrBlankNode, Quad, Term, TermOrVariable, + Variable, }; use spargebra::GraphUpdateOperation; use std::collections::HashMap; @@ -99,9 +100,9 @@ impl<'a> SimpleUpdateEvaluator<'a> { Ok(()) } - fn eval_delete_data(&mut self, data: &[Quad]) -> Result<(), EvaluationError> { + fn eval_delete_data(&mut self, data: &[GroundQuad]) -> Result<(), EvaluationError> { for quad in data { - let quad = self.encode_quad_for_deletion(quad)?; + let quad = self.encode_quad_for_deletion(quad); self.storage.remove(&quad)?; } Ok(()) @@ -109,7 +110,7 @@ impl<'a> SimpleUpdateEvaluator<'a> { fn eval_delete_insert( &mut self, - delete: &[QuadPattern], + delete: &[GroundQuadPattern], insert: &[QuadPattern], using: &QueryDataset, algebra: &GraphPattern, @@ -146,8 +147,7 @@ impl<'a> SimpleUpdateEvaluator<'a> { .collect::, EvaluationError>>()?; for quad in delete { - if let Some(quad) = - self.encode_quad_pattern_for_deletion(quad, &variables, &tuple)? + if let Some(quad) = self.encode_quad_pattern_for_deletion(quad, &variables, &tuple) { self.storage.remove(&quad)?; } @@ -164,11 +164,7 @@ impl<'a> SimpleUpdateEvaluator<'a> { Ok(()) } - fn eval_load( - &mut self, - from: &NamedNode, - to: &Option, - ) -> Result<(), EvaluationError> { + fn eval_load(&mut self, from: &NamedNode, to: &GraphName) -> Result<(), EvaluationError> { let request = Request::builder() .method(Method::GET) .uri(&from.iri) @@ -201,10 +197,9 @@ impl<'a> SimpleUpdateEvaluator<'a> { from, content_type )) })?; - let to_graph_name = if let Some(graph_name) = to { - NamedNodeRef::new_unchecked(&graph_name.iri).into() - } else { - GraphNameRef::DefaultGraph + let to_graph_name = match to { + GraphName::NamedNode(graph_name) => NamedNodeRef::new_unchecked(&graph_name.iri).into(), + GraphName::DefaultGraph => GraphNameRef::DefaultGraph, }; load_graph( self.storage, @@ -365,16 +360,12 @@ impl<'a> SimpleUpdateEvaluator<'a> { } else { return Ok(None); }, - graph_name: if let Some(graph_name) = &quad.graph_name { - if let Some(graph_name) = - self.encode_named_node_or_var_for_insertion(graph_name, variables, values)? - { - graph_name - } else { - return Ok(None); - } + graph_name: if let Some(graph_name) = + self.encode_graph_name_or_var_for_insertion(&quad.graph_name, variables, values)? + { + graph_name } else { - EncodedTerm::DefaultGraph + return Ok(None); }, })) } @@ -388,27 +379,14 @@ impl<'a> SimpleUpdateEvaluator<'a> { validate: impl FnOnce(&EncodedTerm) -> bool, ) -> Result, EvaluationError> { Ok(match term { - TermOrVariable::Term(term) => Some(match term { - Term::NamedNode(term) => self.encode_named_node_for_insertion(term)?, - Term::BlankNode(bnode) => self - .storage + TermOrVariable::NamedNode(term) => Some(self.encode_named_node_for_insertion(term)?), + TermOrVariable::BlankNode(bnode) => Some( + self.storage .encode_blank_node(bnodes.entry(bnode.clone()).or_default().as_ref())?, - Term::Literal(term) => self.encode_literal_for_insertion(term)?, - }), + ), + TermOrVariable::Literal(term) => Some(self.encode_literal_for_insertion(term)?), 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 - } + self.lookup_variable(v, variables, values).filter(validate) } }) } @@ -423,24 +401,48 @@ impl<'a> SimpleUpdateEvaluator<'a> { NamedNodeOrVariable::NamedNode(term) => { Some(self.encode_named_node_for_insertion(term)?) } - 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 - } + NamedNodeOrVariable::Variable(v) => self + .lookup_variable(v, variables, values) + .filter(|value| value.is_named_node()), + }) + } + + fn encode_graph_name_or_var_for_insertion( + &mut self, + term: &GraphNameOrVariable, + variables: &[Variable], + values: &[Option], + ) -> Result, EvaluationError> { + Ok(match term { + GraphNameOrVariable::NamedNode(term) => { + Some(self.encode_named_node_for_insertion(term)?) } + GraphNameOrVariable::DefaultGraph => Some(EncodedTerm::DefaultGraph), + GraphNameOrVariable::Variable(v) => self + .lookup_variable(v, variables, values) + .filter(|value| value.is_named_node()), }) } + fn lookup_variable( + &self, + v: &Variable, + variables: &[Variable], + values: &[Option], + ) -> Option { + { + 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_insertion( &mut self, term: &NamedNode, @@ -465,105 +467,56 @@ impl<'a> SimpleUpdateEvaluator<'a> { })?) } - fn encode_quad_for_deletion(&mut self, quad: &Quad) -> Result { - Ok(EncodedQuad { - subject: match &quad.subject { - NamedOrBlankNode::NamedNode(subject) => { - self.encode_named_node_for_deletion(subject) - } - NamedOrBlankNode::BlankNode(_) => { - return Err(EvaluationError::msg( - "Blank nodes are not allowed in DELETE DATA", - )) - } - }, + fn encode_quad_for_deletion(&mut self, quad: &GroundQuad) -> EncodedQuad { + EncodedQuad { + subject: self.encode_named_node_for_deletion(&quad.subject), predicate: self.encode_named_node_for_deletion(&quad.predicate), object: match &quad.object { - Term::NamedNode(object) => self.encode_named_node_for_deletion(object), - Term::BlankNode(_) => { - return Err(EvaluationError::msg( - "Blank nodes are not allowed in DELETE DATA", - )) - } - Term::Literal(object) => self.encode_literal_for_deletion(object), + GroundTerm::NamedNode(object) => self.encode_named_node_for_deletion(object), + GroundTerm::Literal(object) => self.encode_literal_for_deletion(object), }, graph_name: match &quad.graph_name { GraphName::NamedNode(graph_name) => self.encode_named_node_for_deletion(graph_name), GraphName::DefaultGraph => EncodedTerm::DefaultGraph, }, - }) + } } fn encode_quad_pattern_for_deletion( &self, - quad: &QuadPattern, + quad: &GroundQuadPattern, variables: &[Variable], values: &[Option], - ) -> Result, EvaluationError> { - Ok(Some(EncodedQuad { - subject: if let Some(subject) = - self.encode_term_or_var_for_deletion(&quad.subject, variables, values)? - { - subject - } else { - return Ok(None); - }, - predicate: if let Some(predicate) = - self.encode_named_node_or_var_for_deletion(&quad.predicate, variables, values) - { - predicate - } else { - return Ok(None); - }, - object: if let Some(object) = - self.encode_term_or_var_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_or_var_for_deletion(graph_name, variables, values) - { - graph_name - } else { - return Ok(None); - } - } else { - EncodedTerm::DefaultGraph - }, - })) + ) -> Option { + Some(EncodedQuad { + subject: self.encode_term_or_var_for_deletion(&quad.subject, variables, values)?, + predicate: self.encode_named_node_or_var_for_deletion( + &quad.predicate, + variables, + values, + )?, + object: self.encode_term_or_var_for_deletion(&quad.object, variables, values)?, + graph_name: self.encode_graph_name_or_var_for_deletion( + &quad.graph_name, + variables, + values, + )?, + }) } fn encode_term_or_var_for_deletion( &self, - term: &TermOrVariable, + term: &GroundTermOrVariable, variables: &[Variable], values: &[Option], - ) -> Result, EvaluationError> { - Ok(match term { - TermOrVariable::Term(term) => match term { - Term::NamedNode(term) => Some(self.encode_named_node_for_deletion(term)), - Term::BlankNode(_) => { - return Err(EvaluationError::msg( - "Blank nodes are not allowed in DELETE patterns", - )) - } - Term::Literal(term) => Some(self.encode_literal_for_deletion(term)), - }, - TermOrVariable::Variable(v) => { - if let Some(Some(term)) = variables - .iter() - .position(|v2| v == v2) - .and_then(|i| values.get(i)) - { - Some(*term) - } else { - None - } + ) -> Option { + match term { + GroundTermOrVariable::NamedNode(term) => { + Some(self.encode_named_node_for_deletion(term)) } - }) + GroundTermOrVariable::Literal(term) => Some(self.encode_literal_for_deletion(term)), + GroundTermOrVariable::Variable(v) => self.lookup_variable(v, variables, values), + } } fn encode_named_node_or_var_for_deletion( @@ -574,21 +527,24 @@ impl<'a> SimpleUpdateEvaluator<'a> { ) -> Option { match term { NamedNodeOrVariable::NamedNode(term) => Some(self.encode_named_node_for_deletion(term)), - 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 - } - } + NamedNodeOrVariable::Variable(v) => self + .lookup_variable(v, variables, values) + .filter(|v| v.is_named_node()), + } + } + + fn encode_graph_name_or_var_for_deletion( + &self, + graph_name: &GraphNameOrVariable, + variables: &[Variable], + values: &[Option], + ) -> Option { + match graph_name { + GraphNameOrVariable::NamedNode(term) => Some(self.encode_named_node_for_deletion(term)), + GraphNameOrVariable::DefaultGraph => Some(EncodedTerm::DefaultGraph), + GraphNameOrVariable::Variable(v) => self + .lookup_variable(v, variables, values) + .filter(|v| v.is_named_node()), } } diff --git a/lib/src/store.rs b/lib/src/store.rs index 2b4c0ed8..0d288f9b 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -171,10 +171,10 @@ impl Store { ) -> QuadIter { QuadIter { iter: self.storage.quads_for_pattern( - subject.map(|s| get_encoded_named_or_blank_node(s)), - predicate.map(|p| get_encoded_named_node(p)), - object.map(|o| get_encoded_term(o)), - graph_name.map(|g| get_encoded_graph_name(g)), + subject.map(get_encoded_named_or_blank_node), + predicate.map(get_encoded_named_node), + object.map(get_encoded_term), + graph_name.map(get_encoded_graph_name), ), storage: self.storage.clone(), } diff --git a/spargebra/src/algebra.rs b/spargebra/src/algebra.rs index 4a572c64..55b59209 100644 --- a/spargebra/src/algebra.rs +++ b/spargebra/src/algebra.rs @@ -55,7 +55,7 @@ pub struct QuadPattern { pub subject: TermOrVariable, pub predicate: NamedNodeOrVariable, pub object: TermOrVariable, - pub graph_name: Option, + pub graph_name: GraphNameOrVariable, } impl QuadPattern { @@ -63,31 +63,58 @@ impl QuadPattern { subject: impl Into, predicate: impl Into, object: impl Into, - graph_name: Option, + graph_name: impl Into, ) -> Self { Self { subject: subject.into(), predicate: predicate.into(), object: object.into(), - graph_name, + graph_name: graph_name.into(), } } } impl fmt::Display for QuadPattern { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(graph_name) = &self.graph_name { + if self.graph_name == GraphNameOrVariable::DefaultGraph { write!( f, - "(graph {} (triple {} {} {}))", - graph_name, self.subject, self.predicate, self.object + "(triple {} {} {})", + self.subject, self.predicate, self.object ) } else { + write!( + f, + "(graph {} (triple {} {} {}))", + self.graph_name, self.subject, self.predicate, self.object + ) + } + } +} + +/// A [triple pattern](https://www.w3.org/TR/sparql11-query/#defn_TriplePattern) in a specific graph without blank nodes +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub struct GroundQuadPattern { + pub subject: GroundTermOrVariable, + pub predicate: NamedNodeOrVariable, + pub object: GroundTermOrVariable, + pub graph_name: GraphNameOrVariable, +} + +impl fmt::Display for GroundQuadPattern { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.graph_name == GraphNameOrVariable::DefaultGraph { write!( f, "(triple {} {} {})", self.subject, self.predicate, self.object ) + } else { + write!( + f, + "(graph {} (triple {} {} {}))", + self.graph_name, self.subject, self.predicate, self.object + ) } } } @@ -96,18 +123,38 @@ pub(crate) struct SparqlQuadPattern<'a>(pub(crate) &'a QuadPattern); impl<'a> fmt::Display for SparqlQuadPattern<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(graph_name) = &self.0.graph_name { + if self.0.graph_name == GraphNameOrVariable::DefaultGraph { write!( f, - "GRAPH {} {{ {} {} {} }}", - graph_name, self.0.subject, self.0.predicate, self.0.object + "{} {} {} .", + self.0.subject, self.0.predicate, self.0.object ) } else { + write!( + f, + "GRAPH {} {{ {} {} {} }}", + self.0.graph_name, self.0.subject, self.0.predicate, self.0.object + ) + } + } +} + +pub(crate) struct SparqlGroundQuadPattern<'a>(pub(crate) &'a GroundQuadPattern); + +impl<'a> fmt::Display for SparqlGroundQuadPattern<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0.graph_name == GraphNameOrVariable::DefaultGraph { write!( f, "{} {} {} .", self.0.subject, self.0.predicate, self.0.object ) + } else { + write!( + f, + "GRAPH {} {{ {} {} {} }}", + self.0.graph_name, self.0.subject, self.0.predicate, self.0.object + ) } } } @@ -566,7 +613,7 @@ pub enum GraphPattern { /// A table used to provide inline values Table { variables: Vec, - rows: Vec>>, + rows: Vec>>, }, /// [OrderBy](https://www.w3.org/TR/sparql11-query/#defn_algOrdered) OrderBy { @@ -1329,3 +1376,12 @@ impl From for GraphTarget { Self::NamedNode(node) } } + +impl From for GraphTarget { + fn from(graph_name: GraphName) -> Self { + match graph_name { + GraphName::NamedNode(node) => Self::NamedNode(node), + GraphName::DefaultGraph => Self::DefaultGraph, + } + } +} diff --git a/spargebra/src/parser.rs b/spargebra/src/parser.rs index 4caf5623..884e0f3b 100644 --- a/spargebra/src/parser.rs +++ b/spargebra/src/parser.rs @@ -454,7 +454,10 @@ fn build_select( m } -fn copy_graph(from: Option, to: Option) -> GraphUpdateOperation { +fn copy_graph( + from: impl Into, + to: impl Into, +) -> GraphUpdateOperation { let bgp = GraphPattern::Bgp(vec![TriplePattern::new( Variable { name: "s".into() }, Variable { name: "p".into() }, @@ -469,13 +472,12 @@ fn copy_graph(from: Option, to: Option) -> Graph to, )], using: None, - pattern: Box::new(if let Some(from) = from { - GraphPattern::Graph { + pattern: Box::new(match from.into() { + GraphName::NamedNode(from) => GraphPattern::Graph { graph_name: from.into(), inner: Box::new(bgp), - } - } else { - bgp + }, + GraphName::DefaultGraph => bgp, }), } } @@ -968,7 +970,7 @@ parser! { //[31] rule Load() -> Vec = i("LOAD") _ silent:Update1_silent() _ from:iri() _ to:Load_to()? { - vec![GraphUpdateOperation::Load { silent, from, to }] + vec![GraphUpdateOperation::Load { silent, from, to: to.map_or(GraphName::DefaultGraph, GraphName::NamedNode) }] } rule Load_to() -> NamedNode = i("INTO") _ g: GraphRef() { g } @@ -994,7 +996,7 @@ parser! { Vec::new() // identity case } else { let bgp = GraphPattern::Bgp(vec![TriplePattern::new(Variable { name: "s".into() }, Variable { name: "p".into() }, Variable { name: "o".into() })]); - vec![copy_graph(from, to.map(NamedNodeOrVariable::NamedNode))] + vec![copy_graph(from, to)] } } @@ -1005,7 +1007,7 @@ parser! { Vec::new() // identity case } else { let bgp = GraphPattern::Bgp(vec![TriplePattern::new(Variable { name: "s".into() }, Variable { name: "p".into() }, Variable { name: "o".into() })]); - vec![GraphUpdateOperation::Drop { silent: true, graph: to.clone().map_or(GraphTarget::DefaultGraph, GraphTarget::NamedNode) }, copy_graph(from.clone(), to.map(NamedNodeOrVariable::NamedNode)), GraphUpdateOperation::Drop { silent, graph: from.map_or(GraphTarget::DefaultGraph, GraphTarget::NamedNode) }] + vec![GraphUpdateOperation::Drop { silent: true, graph: to.clone().into() }, copy_graph(from.clone(), to), GraphUpdateOperation::Drop { silent, graph: from.into() }] } } @@ -1016,7 +1018,7 @@ parser! { Vec::new() // identity case } else { let bgp = GraphPattern::Bgp(vec![TriplePattern::new(Variable { name: "s".into() }, Variable { name: "p".into() }, Variable{ name: "o".into() })]); - vec![GraphUpdateOperation::Drop { silent: true, graph: to.clone().map_or(GraphTarget::DefaultGraph, GraphTarget::NamedNode) }, copy_graph(from, to.map(NamedNodeOrVariable::NamedNode))] + vec![GraphUpdateOperation::Drop { silent: true, graph: to.clone().into() }, copy_graph(from, to)] } } @@ -1026,34 +1028,42 @@ parser! { } //[39] - rule DeleteData() -> Vec = i("DELETE") _ i("DATA") _ data:QuadData() {? - if data.iter().any(|quad| matches!(quad.subject, NamedOrBlankNode::BlankNode(_)) || matches!(quad.object, Term::BlankNode(_))) { - Err("Blank nodes are not allowed in DELETE DATA") - } else { - Ok(vec![GraphUpdateOperation::DeleteData { data }]) - } + rule DeleteData() -> Vec = i("DELETE") _ i("DATA") _ data:GroundQuadData() { + vec![GraphUpdateOperation::DeleteData { data }] } //[40] rule DeleteWhere() -> Vec = i("DELETE") _ i("WHERE") _ d:QuadPattern() {? - if d.iter().any(|quad| matches!(quad.subject, TermOrVariable::Term(Term::BlankNode(_))) || matches!(quad.object, TermOrVariable::Term(Term::BlankNode(_)))) { - Err("Blank nodes are not allowed in DELETE WHERE") - } else { - let pattern = d.iter().map(|q| { - let bgp = GraphPattern::Bgp(vec![TriplePattern::new(q.subject.clone(), q.predicate.clone(), q.object.clone())]); - if let Some(graph_name) = &q.graph_name { - GraphPattern::Graph { graph_name: graph_name.clone(), inner: Box::new(bgp) } - } else { - bgp - } - }).fold(GraphPattern::Bgp(Vec::new()), new_join); - Ok(vec![GraphUpdateOperation::DeleteInsert { - delete: d, - insert: Vec::new(), - using: None, - pattern: Box::new(pattern) - }]) - } + let pattern = d.iter().map(|q| { + let bgp = GraphPattern::Bgp(vec![TriplePattern::new(q.subject.clone(), q.predicate.clone(), q.object.clone())]); + match &q.graph_name { + GraphNameOrVariable::NamedNode(graph_name) => GraphPattern::Graph { graph_name: graph_name.clone().into(), inner: Box::new(bgp) }, + GraphNameOrVariable::DefaultGraph => bgp, + GraphNameOrVariable::Variable(graph_name) => GraphPattern::Graph { graph_name: graph_name.clone().into(), inner: Box::new(bgp) }, + } + }).fold(GraphPattern::Bgp(Vec::new()), new_join); + let delete = d.into_iter().map(|q| Ok(GroundQuadPattern { + subject: match q.subject { + TermOrVariable::NamedNode(subject) => subject.into(), + TermOrVariable::BlankNode(_) => return Err("Blank nodes are not allowed in DELETE WHERE"), + TermOrVariable::Literal(subject) => subject.into(), + TermOrVariable::Variable(subject) => subject.into(), + }, + predicate: q.predicate, + object: match q.object { + TermOrVariable::NamedNode(object) => object.into(), + TermOrVariable::BlankNode(_) => return Err("Blank nodes are not allowed in DELETE WHERE"), + TermOrVariable::Literal(object) => object.into(), + TermOrVariable::Variable(object) => object.into(), + }, + graph_name: q.graph_name + })).collect::,_>>()?; + Ok(vec![GraphUpdateOperation::DeleteInsert { + delete, + insert: Vec::new(), + using: None, + pattern: Box::new(pattern) + }]) } //[41] @@ -1081,13 +1091,23 @@ parser! { 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())) + delete = delete.into_iter().map(|q| if q.graph_name == GraphNameOrVariable::DefaultGraph { + GroundQuadPattern { + subject: q.subject, + predicate: q.predicate, + object: q.object, + graph_name: 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())) + insert = insert.into_iter().map(|q| if q.graph_name == GraphNameOrVariable::DefaultGraph { + QuadPattern { + subject: q.subject, + predicate: q.predicate, + object: q.object, + graph_name: with.clone().into() + } } else { q }).collect(); @@ -1104,7 +1124,7 @@ parser! { }] } rule Modify_with() -> NamedNode = i("WITH") _ i:iri() _ { i } - rule Modify_clauses() -> (Option>, Option>) = d:DeleteClause() _ i:InsertClause()? { + rule Modify_clauses() -> (Option>, Option>) = d:DeleteClause() _ i:InsertClause()? { (Some(d), i) } / i:InsertClause() { (None, Some(i)) @@ -1115,12 +1135,23 @@ parser! { } //[42] - rule DeleteClause() -> Vec = i("DELETE") _ q:QuadPattern() {? - if q.iter().any(|quad| matches!(quad.subject, TermOrVariable::Term(Term::BlankNode(_))) || matches!(quad.object, TermOrVariable::Term(Term::BlankNode(_)))) { - Err("Blank nodes are not allowed in DELETE") - } else { - Ok(q) - } + rule DeleteClause() -> Vec = i("DELETE") _ q:QuadPattern() {? + q.into_iter().map(|q| Ok(GroundQuadPattern { + subject: match q.subject { + TermOrVariable::NamedNode(subject) => subject.into(), + TermOrVariable::BlankNode(_) => return Err("Blank nodes are not allowed in DELETE WHERE"), + TermOrVariable::Literal(subject) => subject.into(), + TermOrVariable::Variable(subject) => subject.into(), + }, + predicate: q.predicate, + object: match q.object { + TermOrVariable::NamedNode(object) => object.into(), + TermOrVariable::BlankNode(_) => return Err("Blank nodes are not allowed in DELETE WHERE"), + TermOrVariable::Literal(object) => object.into(), + TermOrVariable::Variable(object) => object.into(), + }, + graph_name: q.graph_name + })).collect::,_>>() } //[43] @@ -1136,10 +1167,10 @@ parser! { } //[45] - rule GraphOrDefault() -> Option = i("DEFAULT") { - None + rule GraphOrDefault() -> GraphName = i("DEFAULT") { + GraphName::DefaultGraph } / (i("GRAPH") _)? g:iri() { - Some(g) + GraphName::NamedNode(g) } //[46] @@ -1158,24 +1189,49 @@ parser! { rule QuadData() -> Vec = "{" _ q:Quads() _ "}" {? q.into_iter().map(|q| Ok(Quad { subject: match q.subject { - TermOrVariable::Term(Term::NamedNode(t)) => t.into(), - TermOrVariable::Term(Term::BlankNode(t)) => t.into(), - _ => return Err(()) + TermOrVariable::NamedNode(t) => t.into(), + TermOrVariable::BlankNode(t) => t.into(), + TermOrVariable::Literal(_) | TermOrVariable::Variable(_) => return Err(()) }, predicate: if let NamedNodeOrVariable::NamedNode(t) = q.predicate { t } else { return Err(()) }, - object: if let TermOrVariable::Term(t) = q.object { + object: match q.object { + TermOrVariable::NamedNode(t) => t.into(), + TermOrVariable::BlankNode(t) => t.into(), + TermOrVariable::Literal(t) => t.into(), + TermOrVariable::Variable(_) => return Err(()) + }, + graph_name: match q.graph_name { + GraphNameOrVariable::NamedNode(t) => t.into(), + GraphNameOrVariable::DefaultGraph => GraphName::DefaultGraph, + GraphNameOrVariable::Variable(_) => return Err(()) + } + })).collect::, ()>>().map_err(|_| "Variables are not allowed in INSERT DATA and DELETE DATA") + } + rule GroundQuadData() -> Vec = "{" _ q:Quads() _ "}" {? + q.into_iter().map(|q| Ok(GroundQuad { + subject: if let TermOrVariable::NamedNode(t) = q.subject { + t + } else { + return Err(()) + }, + predicate: if let NamedNodeOrVariable::NamedNode(t) = q.predicate { t } else { return Err(()) }, + object: match q.object { + TermOrVariable::NamedNode(t) => t.into(), + TermOrVariable::Literal(t) => t.into(), + TermOrVariable::BlankNode(_) | TermOrVariable::Variable(_) => return Err(()) + }, graph_name: match q.graph_name { - Some(NamedNodeOrVariable::NamedNode(t)) => t.into(), - None => GraphName::DefaultGraph, - _ => return Err(()) + GraphNameOrVariable::NamedNode(t) => t.into(), + GraphNameOrVariable::DefaultGraph => GraphName::DefaultGraph, + GraphNameOrVariable::Variable(_) => return Err(()) } })).collect::, ()>>().map_err(|_| "Variables are not allowed in INSERT DATA and DELETE DATA") } @@ -1185,13 +1241,13 @@ parser! { q.into_iter().flatten().collect() } rule Quads_TriplesTemplate() -> Vec = t:TriplesTemplate() { - t.into_iter().map(|t| QuadPattern::new(t.subject, t.predicate, t.object, None)).collect() + t.into_iter().map(|t| QuadPattern::new(t.subject, t.predicate, t.object, GraphNameOrVariable::DefaultGraph)).collect() } //TODO: return iter? rule Quads_QuadsNotTriples() -> Vec = q:QuadsNotTriples() _ "."? { q } //[51] rule QuadsNotTriples() -> Vec = i("GRAPH") _ g:VarOrIri() _ "{" _ t:TriplesTemplate()? _ "}" { - t.unwrap_or_else(Vec::new).into_iter().map(|t| QuadPattern::new(t.subject, t.predicate, t.object, Some(g.clone()))).collect() + t.unwrap_or_else(Vec::new).into_iter().map(|t| QuadPattern::new(t.subject, t.predicate, t.object, g.clone())).collect() } //[52] @@ -1305,21 +1361,21 @@ parser! { } //[63] - rule InlineDataOneVar() -> (Vec, Vec>>) = var:Var() _ "{" _ d:InlineDataOneVar_value()* "}" { + rule InlineDataOneVar() -> (Vec, Vec>>) = var:Var() _ "{" _ d:InlineDataOneVar_value()* "}" { (vec![var], d) } - rule InlineDataOneVar_value() -> Vec> = t:DataBlockValue() _ { vec![t] } + rule InlineDataOneVar_value() -> Vec> = t:DataBlockValue() _ { vec![t] } //[64] - rule InlineDataFull() -> (Vec, Vec>>) = "(" _ vars:InlineDataFull_var()* _ ")" _ "{" _ val:InlineDataFull_values()* "}" { + rule InlineDataFull() -> (Vec, Vec>>) = "(" _ vars:InlineDataFull_var()* _ ")" _ "{" _ val:InlineDataFull_values()* "}" { (vars, val) } rule InlineDataFull_var() -> Variable = v:Var() _ { v } - rule InlineDataFull_values() -> Vec> = "(" _ v:InlineDataFull_value()* _ ")" _ { v } - rule InlineDataFull_value() -> Option = v:DataBlockValue() _ { v } + rule InlineDataFull_values() -> Vec> = "(" _ v:InlineDataFull_value()* _ ")" _ { v } + rule InlineDataFull_value() -> Option = v:DataBlockValue() _ { v } //[65] - rule DataBlockValue() -> Option = + rule DataBlockValue() -> Option = i:iri() { Some(i.into()) } / l:RDFLiteral() { Some(l.into()) } / l:NumericLiteral() { Some(l.into()) } / diff --git a/spargebra/src/term.rs b/spargebra/src/term.rs index 5e55d150..ae5a8216 100644 --- a/spargebra/src/term.rs +++ b/spargebra/src/term.rs @@ -163,14 +163,14 @@ impl fmt::Display for NamedOrBlankNode { impl From for NamedOrBlankNode { #[inline] fn from(node: NamedNode) -> Self { - NamedOrBlankNode::NamedNode(node) + Self::NamedNode(node) } } impl From for NamedOrBlankNode { #[inline] fn from(node: BlankNode) -> Self { - NamedOrBlankNode::BlankNode(node) + Self::BlankNode(node) } } @@ -178,32 +178,32 @@ impl From for NamedOrBlankNode { /// /// The default string formatter is returning an N-Triples, Turtle and SPARQL compatible representation. #[derive(Eq, PartialEq, Debug, Clone, Hash)] -pub enum NamedNodeOrLiteral { +pub enum GroundTerm { NamedNode(NamedNode), Literal(Literal), } -impl fmt::Display for NamedNodeOrLiteral { +impl fmt::Display for GroundTerm { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - NamedNodeOrLiteral::NamedNode(node) => node.fmt(f), - NamedNodeOrLiteral::Literal(literal) => literal.fmt(f), + GroundTerm::NamedNode(node) => node.fmt(f), + GroundTerm::Literal(literal) => literal.fmt(f), } } } -impl From for NamedNodeOrLiteral { +impl From for GroundTerm { #[inline] fn from(node: NamedNode) -> Self { - NamedNodeOrLiteral::NamedNode(node) + Self::NamedNode(node) } } -impl From for NamedNodeOrLiteral { +impl From for GroundTerm { #[inline] fn from(literal: Literal) -> Self { - NamedNodeOrLiteral::Literal(literal) + Self::Literal(literal) } } @@ -233,21 +233,21 @@ impl fmt::Display for Term { impl From for Term { #[inline] fn from(node: NamedNode) -> Self { - Term::NamedNode(node) + Self::NamedNode(node) } } impl From for Term { #[inline] fn from(node: BlankNode) -> Self { - Term::BlankNode(node) + Self::BlankNode(node) } } impl From for Term { #[inline] fn from(literal: Literal) -> Self { - Term::Literal(literal) + Self::Literal(literal) } } @@ -255,8 +255,8 @@ impl From for Term { #[inline] fn from(resource: NamedOrBlankNode) -> Self { match resource { - NamedOrBlankNode::NamedNode(node) => Term::NamedNode(node), - NamedOrBlankNode::BlankNode(node) => Term::BlankNode(node), + NamedOrBlankNode::NamedNode(node) => Self::NamedNode(node), + NamedOrBlankNode::BlankNode(node) => Self::BlankNode(node), } } } @@ -282,7 +282,7 @@ impl fmt::Display for GraphName { impl From for GraphName { #[inline] fn from(node: NamedNode) -> Self { - GraphName::NamedNode(node) + Self::NamedNode(node) } } @@ -327,6 +327,47 @@ impl fmt::Display for Quad { } } +/// A [RDF triple](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-triple) in a [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) without blank nodes. +/// +/// The default string formatter is returning a N-Quads representation. +/// +/// ``` +/// use spargebra::term::NamedNode; +/// use spargebra::term::GroundQuad; +/// +/// assert_eq!( +/// " .", +/// GroundQuad { +/// subject: NamedNode { iri: "http://example.com/foo".into() }.into(), +/// predicate: NamedNode { iri: "http://schema.org/sameAs".into() }, +/// object: NamedNode { iri: "http://example.com/foo".into() }.into(), +/// graph_name: NamedNode { iri: "http://example.com/".into() }.into(), +/// }.to_string() +/// ) +/// ``` +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub struct GroundQuad { + pub subject: NamedNode, + pub predicate: NamedNode, + pub object: GroundTerm, + pub graph_name: GraphName, +} + +impl fmt::Display for GroundQuad { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.graph_name == GraphName::DefaultGraph { + write!(f, "{} {} {} .", self.subject, self.predicate, self.object) + } else { + write!( + f, + "{} {} {} {} .", + self.subject, self.predicate, self.object, self.graph_name + ) + } + } +} + /// The union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) and [variables](https://www.w3.org/TR/sparql11-query/#sparqlQueryVariables). #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub enum NamedNodeOrVariable { @@ -345,27 +386,85 @@ impl fmt::Display for NamedNodeOrVariable { impl From for NamedNodeOrVariable { fn from(node: NamedNode) -> Self { - NamedNodeOrVariable::NamedNode(node) + Self::NamedNode(node) } } impl From for NamedNodeOrVariable { fn from(var: Variable) -> Self { - NamedNodeOrVariable::Variable(var) + Self::Variable(var) + } +} + +/// The union of [terms](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-term) and [variables](https://www.w3.org/TR/sparql11-query/#sparqlQueryVariables). +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub enum GroundTermOrVariable { + NamedNode(NamedNode), + Literal(Literal), + Variable(Variable), +} + +impl fmt::Display for GroundTermOrVariable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + GroundTermOrVariable::NamedNode(term) => term.fmt(f), + GroundTermOrVariable::Literal(term) => term.fmt(f), + GroundTermOrVariable::Variable(var) => var.fmt(f), + } + } +} + +impl From for GroundTermOrVariable { + fn from(node: NamedNode) -> Self { + Self::NamedNode(node) + } +} + +impl From for GroundTermOrVariable { + fn from(literal: Literal) -> Self { + Self::Literal(literal) + } +} + +impl From for GroundTermOrVariable { + fn from(var: Variable) -> Self { + Self::Variable(var) + } +} + +impl From for GroundTermOrVariable { + fn from(term: GroundTerm) -> Self { + match term { + GroundTerm::NamedNode(node) => Self::NamedNode(node), + GroundTerm::Literal(literal) => Self::Literal(literal), + } + } +} + +impl From for GroundTermOrVariable { + fn from(element: NamedNodeOrVariable) -> Self { + match element { + NamedNodeOrVariable::NamedNode(node) => Self::NamedNode(node), + NamedNodeOrVariable::Variable(var) => Self::Variable(var), + } } } /// The union of [terms](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-term) and [variables](https://www.w3.org/TR/sparql11-query/#sparqlQueryVariables). #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub enum TermOrVariable { - Term(Term), + NamedNode(NamedNode), + BlankNode(BlankNode), + Literal(Literal), Variable(Variable), } impl fmt::Display for TermOrVariable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TermOrVariable::Term(term) => term.fmt(f), + TermOrVariable::NamedNode(term) => term.fmt(f), + TermOrVariable::BlankNode(term) => term.fmt(f), + TermOrVariable::Literal(term) => term.fmt(f), TermOrVariable::Variable(var) => var.fmt(f), } } @@ -373,39 +472,91 @@ impl fmt::Display for TermOrVariable { impl From for TermOrVariable { fn from(node: NamedNode) -> Self { - TermOrVariable::Term(node.into()) + Self::NamedNode(node) } } impl From for TermOrVariable { fn from(node: BlankNode) -> Self { - TermOrVariable::Term(node.into()) + Self::BlankNode(node) } } impl From for TermOrVariable { fn from(literal: Literal) -> Self { - TermOrVariable::Term(literal.into()) + Self::Literal(literal) } } impl From for TermOrVariable { fn from(var: Variable) -> Self { - TermOrVariable::Variable(var) + Self::Variable(var) } } impl From for TermOrVariable { fn from(term: Term) -> Self { - TermOrVariable::Term(term) + match term { + Term::NamedNode(node) => Self::NamedNode(node), + Term::BlankNode(node) => Self::BlankNode(node), + Term::Literal(literal) => Self::Literal(literal), + } } } impl From for TermOrVariable { fn from(element: NamedNodeOrVariable) -> Self { match element { - NamedNodeOrVariable::NamedNode(node) => TermOrVariable::Term(node.into()), - NamedNodeOrVariable::Variable(var) => TermOrVariable::Variable(var), + NamedNodeOrVariable::NamedNode(node) => Self::NamedNode(node), + NamedNodeOrVariable::Variable(var) => Self::Variable(var), + } + } +} + +/// The union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri), [default graph name](https://www.w3.org/TR/rdf11-concepts/#dfn-default-graph) and [variables](https://www.w3.org/TR/sparql11-query/#sparqlQueryVariables). +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub enum GraphNameOrVariable { + NamedNode(NamedNode), + DefaultGraph, + Variable(Variable), +} + +impl fmt::Display for GraphNameOrVariable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + GraphNameOrVariable::NamedNode(node) => node.fmt(f), + GraphNameOrVariable::DefaultGraph => f.write_str("DEFAULT"), + GraphNameOrVariable::Variable(var) => var.fmt(f), + } + } +} + +impl From for GraphNameOrVariable { + fn from(node: NamedNode) -> Self { + Self::NamedNode(node) + } +} + +impl From for GraphNameOrVariable { + fn from(var: Variable) -> Self { + Self::Variable(var) + } +} + +impl From for GraphNameOrVariable { + fn from(graph_name: GraphName) -> Self { + match graph_name { + GraphName::NamedNode(node) => Self::NamedNode(node), + GraphName::DefaultGraph => Self::DefaultGraph, + } + } +} + +impl From for GraphNameOrVariable { + fn from(graph_name: NamedNodeOrVariable) -> Self { + match graph_name { + NamedNodeOrVariable::NamedNode(node) => Self::NamedNode(node), + NamedNodeOrVariable::Variable(var) => Self::Variable(var), } } } diff --git a/spargebra/src/update.rs b/spargebra/src/update.rs index 656691ab..04318861 100644 --- a/spargebra/src/update.rs +++ b/spargebra/src/update.rs @@ -70,28 +70,28 @@ impl<'a> TryFrom<&'a String> for Update { /// The [graph update operations](https://www.w3.org/TR/sparql11-update/#formalModelGraphUpdate) #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub enum GraphUpdateOperation { - /// [insert data](https://www.w3.org/TR/sparql11-update/#def_insertdataoperation) + /// [insert data](https://www.w3.org/TR/sparql11-update/#defn_insertDataOperation) InsertData { data: Vec }, - /// [delete data](https://www.w3.org/TR/sparql11-update/#def_deletedataoperation) - DeleteData { data: Vec }, - /// [delete insert](https://www.w3.org/TR/sparql11-update/#def_deleteinsertoperation) + /// [delete data](https://www.w3.org/TR/sparql11-update/#defn_deleteDataOperation) + DeleteData { data: Vec }, + /// [delete insert](https://www.w3.org/TR/sparql11-update/#defn_deleteInsertOperation) DeleteInsert { - delete: Vec, + delete: Vec, insert: Vec, using: Option, pattern: Box, }, - /// [load](https://www.w3.org/TR/sparql11-update/#def_loadoperation) + /// [load](https://www.w3.org/TR/sparql11-update/#defn_loadOperation) Load { silent: bool, from: NamedNode, - to: Option, + to: GraphName, }, - /// [clear](https://www.w3.org/TR/sparql11-update/#def_clearoperation) + /// [clear](https://www.w3.org/TR/sparql11-update/#defn_clearOperation) Clear { silent: bool, graph: GraphTarget }, - /// [create](https://www.w3.org/TR/sparql11-update/#def_createoperation) + /// [create](https://www.w3.org/TR/sparql11-update/#defn_createOperation) Create { silent: bool, graph: NamedNode }, - /// [drop](https://www.w3.org/TR/sparql11-update/#def_dropoperation) + /// [drop](https://www.w3.org/TR/sparql11-update/#defn_dropOperation) Drop { silent: bool, graph: GraphTarget }, } @@ -105,7 +105,7 @@ impl fmt::Display for GraphUpdateOperation { } GraphUpdateOperation::DeleteData { data } => { writeln!(f, "DELETE DATA {{")?; - write_quads(data, f)?; + write_ground_quads(data, f)?; write!(f, "}}") } GraphUpdateOperation::DeleteInsert { @@ -117,7 +117,7 @@ impl fmt::Display for GraphUpdateOperation { if !delete.is_empty() { writeln!(f, "DELETE {{")?; for quad in delete { - writeln!(f, "\t{}", SparqlQuadPattern(quad))?; + writeln!(f, "\t{}", SparqlGroundQuadPattern(quad))?; } writeln!(f, "}}")?; } @@ -153,7 +153,7 @@ impl fmt::Display for GraphUpdateOperation { write!(f, "SILENT ")?; } write!(f, "{}", from)?; - if let Some(to) = to { + if to != &GraphName::DefaultGraph { write!(f, " INTO GRAPH {}", to)?; } Ok(()) @@ -197,3 +197,18 @@ fn write_quads(quads: &[Quad], f: &mut fmt::Formatter<'_>) -> fmt::Result { } Ok(()) } + +fn write_ground_quads(quads: &[GroundQuad], f: &mut fmt::Formatter<'_>) -> fmt::Result { + for quad in quads { + if quad.graph_name == GraphName::DefaultGraph { + writeln!(f, "\t{} {} {} .", quad.subject, quad.predicate, quad.object)?; + } else { + writeln!( + f, + "\tGRAPH {} {{ {} {} {} }}", + quad.graph_name, quad.subject, quad.predicate, quad.object + )?; + } + } + Ok(()) +}