From a97250dcce1120488e385e7d8f8fc3a56dbfa675 Mon Sep 17 00:00:00 2001 From: Tpt Date: Sun, 25 Apr 2021 08:55:22 +0200 Subject: [PATCH] Basic RDF-star support No parsing and SPARQL support yet --- lib/README.md | 2 +- lib/benches/store.rs | 4 +- lib/src/lib.rs | 2 +- lib/src/model/dataset.rs | 151 +++++++++++++++++------ lib/src/model/interning.rs | 135 ++++++++++++++++----- lib/src/model/sophia.rs | 116 +++++++++--------- lib/src/model/triple.rs | 186 +++++++++++++++-------------- lib/src/sparql/csv_results.rs | 25 +++- lib/src/sparql/eval.rs | 17 ++- lib/src/sparql/json_results.rs | 65 ++++++---- lib/src/sparql/update.rs | 2 +- lib/src/sparql/xml_results.rs | 119 +++++++++++------- lib/src/storage/binary_encoder.rs | 24 +++- lib/src/storage/numeric_encoder.rs | 91 +++++++++++++- lib/src/store.rs | 54 +++++---- lib/tests/store.rs | 20 +++- python/src/model.rs | 47 ++++++-- python/tests/test_model.py | 25 ++++ python/tests/test_store.py | 5 +- 19 files changed, 753 insertions(+), 337 deletions(-) diff --git a/lib/README.md b/lib/README.md index e54ad608..8bda2949 100644 --- a/lib/README.md +++ b/lib/README.md @@ -37,7 +37,7 @@ let store = Store::open("example.db")?; // insertion let ex = NamedNode::new("http://example.com")?; -let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None); +let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), GraphName::DefaultGraph); store.insert(&quad)?; // quad filter diff --git a/lib/benches/store.rs b/lib/benches/store.rs index f283fde9..2d71fc50 100644 --- a/lib/benches/store.rs +++ b/lib/benches/store.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use oxigraph::model::{Dataset, Graph, NamedNode, Quad, Triple}; +use oxigraph::model::{Dataset, Graph, GraphName, NamedNode, Quad, Triple}; use oxigraph::store::Store; use rand::random; use std::iter::FromIterator; @@ -80,7 +80,7 @@ fn create_quads(size: u64) -> Vec { "http://example.com/id/{}", random::() % size )), - None, + GraphName::DefaultGraph, ) }) .collect() diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 357c8c8b..547cf8d1 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -21,7 +21,7 @@ //! //! // insertion //! let ex = NamedNode::new("http://example.com")?; -//! let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None); +//! let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), GraphName::DefaultGraph); //! store.insert(&quad)?; //! //! // quad filter diff --git a/lib/src/model/dataset.rs b/lib/src/model/dataset.rs index 147e1cb3..2edc00d8 100644 --- a/lib/src/model/dataset.rs +++ b/lib/src/model/dataset.rs @@ -29,7 +29,6 @@ use crate::io::{ use crate::model::interning::*; use crate::model::SubjectRef; use crate::model::*; -use lasso::Rodeo; use std::collections::hash_map::DefaultHasher; use std::collections::BTreeSet; use std::collections::{HashMap, HashSet}; @@ -66,7 +65,8 @@ use std::{fmt, io}; /// ``` #[derive(Debug, Default)] pub struct Dataset { - interner: Rodeo, + interner: Interner, + triples: HashMap, gspo: BTreeSet<( InternedGraphName, InternedSubject, @@ -607,9 +607,13 @@ impl Dataset { for (g, s, _, o) in &self.gspo { if let InternedSubject::BlankNode(bnode) = s { bnodes.insert(*bnode); + } else if let InternedSubject::Triple(triple) = s { + self.triple_blank_nodes(triple, &mut bnodes); } if let InternedTerm::BlankNode(bnode) = o { bnodes.insert(*bnode); + } else if let InternedTerm::Triple(triple) = o { + self.triple_blank_nodes(triple, &mut bnodes); } if let InternedGraphName::BlankNode(bnode) = g { bnodes.insert(*bnode); @@ -618,6 +622,19 @@ impl Dataset { bnodes } + fn triple_blank_nodes(&self, triple: &InternedTriple, bnodes: &mut HashSet) { + if let InternedSubject::BlankNode(bnode) = &triple.subject { + bnodes.insert(*bnode); + } else if let InternedSubject::Triple(t) = &triple.subject { + self.triple_blank_nodes(t, bnodes); + } + if let InternedTerm::BlankNode(bnode) = &triple.object { + bnodes.insert(*bnode); + } else if let InternedTerm::Triple(t) = &triple.object { + self.triple_blank_nodes(t, bnodes); + } + } + fn hash_bnodes( &self, mut hashes: HashMap, @@ -688,7 +705,9 @@ impl Dataset { bnodes_hash: &HashMap, ) -> u64 { if let InternedSubject::BlankNode(bnode) = node { - *bnodes_hash.get(bnode).unwrap() + bnodes_hash[bnode] + } else if let InternedSubject::Triple(triple) = node { + self.hash_triple(triple, bnodes_hash) } else { self.hash_tuple(node.decode_from(&self.interner)) } @@ -696,7 +715,9 @@ impl Dataset { fn hash_term(&self, term: &InternedTerm, bnodes_hash: &HashMap) -> u64 { if let InternedTerm::BlankNode(bnode) = term { - *bnodes_hash.get(bnode).unwrap() + bnodes_hash[bnode] + } else if let InternedTerm::Triple(triple) = term { + self.hash_triple(triple, bnodes_hash) } else { self.hash_tuple(term.decode_from(&self.interner)) } @@ -708,12 +729,24 @@ impl Dataset { bnodes_hash: &HashMap, ) -> u64 { if let InternedGraphName::BlankNode(bnode) = graph_name { - *bnodes_hash.get(bnode).unwrap() + bnodes_hash[bnode] } else { self.hash_tuple(graph_name.decode_from(&self.interner)) } } + fn hash_triple( + &self, + triple: &InternedTriple, + bnodes_hash: &HashMap, + ) -> u64 { + self.hash_tuple(( + self.hash_subject(&triple.subject, bnodes_hash), + self.hash_named_node(&triple.predicate), + self.hash_term(&triple.object, bnodes_hash), + )) + } + fn hash_tuple(&self, v: impl Hash) -> u64 { let mut hasher = DefaultHasher::new(); v.hash(&mut hasher); @@ -775,12 +808,22 @@ impl Dataset { ( if let InternedSubject::BlankNode(bnode) = s { InternedSubject::BlankNode(self.map_bnode(&bnode, hashes)) + } else if let InternedSubject::Triple(triple) = s { + InternedSubject::Triple(Box::new(InternedTriple::encoded_into( + self.label_triple(&triple, hashes).as_ref(), + &mut self.interner, + ))) } else { s }, p, if let InternedTerm::BlankNode(bnode) = o { InternedTerm::BlankNode(self.map_bnode(&bnode, hashes)) + } else if let InternedTerm::Triple(triple) = o { + InternedTerm::Triple(Box::new(InternedTriple::encoded_into( + self.label_triple(&triple, hashes).as_ref(), + &mut self.interner, + ))) } else { o }, @@ -796,16 +839,48 @@ impl Dataset { quads } + fn label_triple( + &mut self, + triple: &InternedTriple, + hashes: &HashMap, + ) -> Triple { + Triple { + subject: if let InternedSubject::BlankNode(bnode) = &triple.subject { + self.gen_bnode(bnode, hashes).into() + } else if let InternedSubject::Triple(t) = &triple.subject { + self.label_triple(t, hashes).into() + } else { + triple.subject.decode_from(&self.interner).into_owned() + }, + predicate: triple.predicate.decode_from(&self.interner).into_owned(), + object: if let InternedTerm::BlankNode(bnode) = &triple.object { + self.gen_bnode(bnode, hashes).into() + } else if let InternedTerm::Triple(t) = &triple.object { + self.label_triple(t, hashes).into() + } else { + triple.object.decode_from(&self.interner).into_owned() + }, + } + } + fn map_bnode( &mut self, old_bnode: &InternedBlankNode, hashes: &HashMap, ) -> InternedBlankNode { InternedBlankNode::encoded_into( - BlankNode::new_from_unique_id(*hashes.get(old_bnode).unwrap()).as_ref(), + self.gen_bnode(old_bnode, hashes).as_ref(), &mut self.interner, ) } + + fn gen_bnode( + &self, + old_bnode: &InternedBlankNode, + hashes: &HashMap, + ) -> BlankNode { + BlankNode::new_from_unique_id(hashes[old_bnode]) + } } impl PartialEq for Dataset { @@ -1151,10 +1226,13 @@ impl<'a> GraphView<'a> { /// Checks if the graph contains the given triple pub fn contains<'b>(&self, triple: impl Into>) -> bool { - if let Some((s, p, o)) = self.encoded_triple(triple.into()) { - self.dataset - .gspo - .contains(&(self.graph_name.clone(), s, p, o)) + if let Some(triple) = self.encoded_triple(triple.into()) { + self.dataset.gspo.contains(&( + self.graph_name.clone(), + triple.subject, + triple.predicate, + triple.object, + )) } else { false } @@ -1195,15 +1273,12 @@ impl<'a> GraphView<'a> { writer.finish() } - fn encoded_triple( - &self, - triple: TripleRef<'_>, - ) -> Option<(InternedSubject, InternedNamedNode, InternedTerm)> { - Some(( - self.dataset.encoded_subject(triple.subject)?, - self.dataset.encoded_named_node(triple.predicate)?, - self.dataset.encoded_term(triple.object)?, - )) + fn encoded_triple(&self, triple: TripleRef<'_>) -> Option { + Some(InternedTriple { + subject: self.dataset.encoded_subject(triple.subject)?, + predicate: self.dataset.encoded_named_node(triple.predicate)?, + object: self.dataset.encoded_term(triple.object)?, + }) } } @@ -1274,16 +1349,24 @@ impl<'a> GraphViewMut<'a> { /// Adds a triple to the graph pub fn insert<'b>(&mut self, triple: impl Into>) -> bool { - let (s, p, o) = self.encode_triple(triple.into()); - self.dataset - .insert_encoded((s, p, o, self.graph_name.clone())) + let triple = self.encode_triple(triple.into()); + self.dataset.insert_encoded(( + triple.subject, + triple.predicate, + triple.object, + self.graph_name.clone(), + )) } /// Removes a concrete triple from the graph pub fn remove<'b>(&mut self, triple: impl Into>) -> bool { - if let Some((s, p, o)) = self.read().encoded_triple(triple.into()) { - self.dataset - .remove_encoded((s, p, o, self.graph_name.clone())) + if let Some(triple) = self.read().encoded_triple(triple.into()) { + self.dataset.remove_encoded(( + triple.subject, + triple.predicate, + triple.object, + self.graph_name.clone(), + )) } else { false } @@ -1332,15 +1415,15 @@ impl<'a> GraphViewMut<'a> { Ok(()) } - fn encode_triple( - &mut self, - triple: TripleRef<'_>, - ) -> (InternedSubject, InternedNamedNode, InternedTerm) { - ( - InternedSubject::encoded_into(triple.subject, &mut self.dataset.interner), - InternedNamedNode::encoded_into(triple.predicate, &mut self.dataset.interner), - InternedTerm::encoded_into(triple.object, &mut self.dataset.interner), - ) + fn encode_triple(&mut self, triple: TripleRef<'_>) -> InternedTriple { + InternedTriple { + subject: InternedSubject::encoded_into(triple.subject, &mut self.dataset.interner), + predicate: InternedNamedNode::encoded_into( + triple.predicate, + &mut self.dataset.interner, + ), + object: InternedTerm::encoded_into(triple.object, &mut self.dataset.interner), + } } /// Returns all the triples contained by the graph diff --git a/lib/src/model/interning.rs b/lib/src/model/interning.rs index 7174c686..f462731b 100644 --- a/lib/src/model/interning.rs +++ b/lib/src/model/interning.rs @@ -2,28 +2,35 @@ use crate::model::*; use lasso::{Key, Rodeo, Spur}; +use std::collections::HashMap; use std::convert::TryInto; +#[derive(Debug, Default)] +pub struct Interner { + strings: Rodeo, + triples: HashMap, +} + #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash)] pub struct InternedNamedNode { id: Spur, } impl InternedNamedNode { - pub fn encoded_into(named_node: NamedNodeRef<'_>, interner: &mut Rodeo) -> Self { + pub fn encoded_into(named_node: NamedNodeRef<'_>, interner: &mut Interner) -> Self { Self { - id: interner.get_or_intern(named_node.as_str()), + id: interner.strings.get_or_intern(named_node.as_str()), } } - pub fn encoded_from(named_node: NamedNodeRef<'_>, interner: &Rodeo) -> Option { + pub fn encoded_from(named_node: NamedNodeRef<'_>, interner: &Interner) -> Option { Some(Self { - id: interner.get(named_node.as_str())?, + id: interner.strings.get(named_node.as_str())?, }) } - pub fn decode_from<'a>(&self, interner: &'a Rodeo) -> NamedNodeRef<'a> { - NamedNodeRef::new_unchecked(interner.resolve(&self.id)) + pub fn decode_from<'a>(&self, interner: &'a Interner) -> NamedNodeRef<'a> { + NamedNodeRef::new_unchecked(interner.strings.resolve(&self.id)) } pub fn first() -> Self { @@ -49,20 +56,20 @@ pub struct InternedBlankNode { } impl InternedBlankNode { - pub fn encoded_into(blank_node: BlankNodeRef<'_>, interner: &mut Rodeo) -> Self { + pub fn encoded_into(blank_node: BlankNodeRef<'_>, interner: &mut Interner) -> Self { Self { - id: interner.get_or_intern(blank_node.as_str()), + id: interner.strings.get_or_intern(blank_node.as_str()), } } - pub fn encoded_from(blank_node: BlankNodeRef<'_>, interner: &Rodeo) -> Option { + pub fn encoded_from(blank_node: BlankNodeRef<'_>, interner: &Interner) -> Option { Some(Self { - id: interner.get(blank_node.as_str())?, + id: interner.strings.get(blank_node.as_str())?, }) } - pub fn decode_from<'a>(&self, interner: &'a Rodeo) -> BlankNodeRef<'a> { - BlankNodeRef::new_unchecked(interner.resolve(&self.id)) + pub fn decode_from<'a>(&self, interner: &'a Interner) -> BlankNodeRef<'a> { + BlankNodeRef::new_unchecked(interner.strings.resolve(&self.id)) } pub fn next(&self) -> Self { @@ -88,13 +95,13 @@ pub enum InternedLiteral { } impl InternedLiteral { - pub fn encoded_into(literal: LiteralRef<'_>, interner: &mut Rodeo) -> Self { - let value_id = interner.get_or_intern(literal.value()); + pub fn encoded_into(literal: LiteralRef<'_>, interner: &mut Interner) -> Self { + let value_id = interner.strings.get_or_intern(literal.value()); if literal.is_plain() { if let Some(language) = literal.language() { Self::LanguageTaggedString { value_id, - language_id: interner.get_or_intern(language), + language_id: interner.strings.get_or_intern(language), } } else { Self::String { value_id } @@ -107,13 +114,13 @@ impl InternedLiteral { } } - pub fn encoded_from(literal: LiteralRef<'_>, interner: &Rodeo) -> Option { - let value_id = interner.get(literal.value())?; + pub fn encoded_from(literal: LiteralRef<'_>, interner: &Interner) -> Option { + let value_id = interner.strings.get(literal.value())?; Some(if literal.is_plain() { if let Some(language) = literal.language() { Self::LanguageTaggedString { value_id, - language_id: interner.get(language)?, + language_id: interner.strings.get(language)?, } } else { Self::String { value_id } @@ -126,20 +133,20 @@ impl InternedLiteral { }) } - pub fn decode_from<'a>(&self, interner: &'a Rodeo) -> LiteralRef<'a> { + pub fn decode_from<'a>(&self, interner: &'a Interner) -> LiteralRef<'a> { match self { InternedLiteral::String { value_id } => { - LiteralRef::new_simple_literal(interner.resolve(value_id)) + LiteralRef::new_simple_literal(interner.strings.resolve(value_id)) } InternedLiteral::LanguageTaggedString { value_id, language_id, } => LiteralRef::new_language_tagged_literal_unchecked( - interner.resolve(value_id), - interner.resolve(language_id), + interner.strings.resolve(value_id), + interner.strings.resolve(language_id), ), InternedLiteral::TypedLiteral { value_id, datatype } => LiteralRef::new_typed_literal( - interner.resolve(value_id), + interner.strings.resolve(value_id), datatype.decode_from(interner), ), } @@ -169,10 +176,11 @@ impl InternedLiteral { pub enum InternedSubject { NamedNode(InternedNamedNode), BlankNode(InternedBlankNode), + Triple(Box), } impl InternedSubject { - pub fn encoded_into(node: SubjectRef<'_>, interner: &mut Rodeo) -> Self { + pub fn encoded_into(node: SubjectRef<'_>, interner: &mut Interner) -> Self { match node { SubjectRef::NamedNode(node) => { Self::NamedNode(InternedNamedNode::encoded_into(node, interner)) @@ -180,10 +188,14 @@ impl InternedSubject { SubjectRef::BlankNode(node) => { Self::BlankNode(InternedBlankNode::encoded_into(node, interner)) } + SubjectRef::Triple(triple) => Self::Triple(Box::new(InternedTriple::encoded_into( + triple.as_ref(), + interner, + ))), } } - pub fn encoded_from(node: SubjectRef<'_>, interner: &Rodeo) -> Option { + pub fn encoded_from(node: SubjectRef<'_>, interner: &Interner) -> Option { Some(match node { SubjectRef::NamedNode(node) => { Self::NamedNode(InternedNamedNode::encoded_from(node, interner)?) @@ -191,13 +203,18 @@ impl InternedSubject { SubjectRef::BlankNode(node) => { Self::BlankNode(InternedBlankNode::encoded_from(node, interner)?) } + SubjectRef::Triple(triple) => Self::Triple(Box::new(InternedTriple::encoded_from( + triple.as_ref(), + interner, + )?)), }) } - pub fn decode_from<'a>(&self, interner: &'a Rodeo) -> SubjectRef<'a> { + pub fn decode_from<'a>(&self, interner: &'a Interner) -> SubjectRef<'a> { match self { Self::NamedNode(node) => SubjectRef::NamedNode(node.decode_from(interner)), Self::BlankNode(node) => SubjectRef::BlankNode(node.decode_from(interner)), + Self::Triple(triple) => SubjectRef::Triple(&interner.triples[triple.as_ref()]), } } @@ -209,6 +226,7 @@ impl InternedSubject { match self { Self::NamedNode(node) => Self::NamedNode(node.next()), Self::BlankNode(node) => Self::BlankNode(node.next()), + Self::Triple(triple) => Self::Triple(Box::new(triple.next())), } } @@ -225,7 +243,7 @@ pub enum InternedGraphName { } impl InternedGraphName { - pub fn encoded_into(node: GraphNameRef<'_>, interner: &mut Rodeo) -> Self { + pub fn encoded_into(node: GraphNameRef<'_>, interner: &mut Interner) -> Self { match node { GraphNameRef::DefaultGraph => Self::DefaultGraph, GraphNameRef::NamedNode(node) => { @@ -237,7 +255,7 @@ impl InternedGraphName { } } - pub fn encoded_from(node: GraphNameRef<'_>, interner: &Rodeo) -> Option { + pub fn encoded_from(node: GraphNameRef<'_>, interner: &Interner) -> Option { Some(match node { GraphNameRef::DefaultGraph => Self::DefaultGraph, GraphNameRef::NamedNode(node) => { @@ -249,7 +267,7 @@ impl InternedGraphName { }) } - pub fn decode_from<'a>(&self, interner: &'a Rodeo) -> GraphNameRef<'a> { + pub fn decode_from<'a>(&self, interner: &'a Interner) -> GraphNameRef<'a> { match self { Self::DefaultGraph => GraphNameRef::DefaultGraph, Self::NamedNode(node) => GraphNameRef::NamedNode(node.decode_from(interner)), @@ -279,10 +297,11 @@ pub enum InternedTerm { NamedNode(InternedNamedNode), BlankNode(InternedBlankNode), Literal(InternedLiteral), + Triple(Box), } impl InternedTerm { - pub fn encoded_into(term: TermRef<'_>, interner: &mut Rodeo) -> Self { + pub fn encoded_into(term: TermRef<'_>, interner: &mut Interner) -> Self { match term { TermRef::NamedNode(term) => { Self::NamedNode(InternedNamedNode::encoded_into(term, interner)) @@ -291,10 +310,14 @@ impl InternedTerm { Self::BlankNode(InternedBlankNode::encoded_into(term, interner)) } TermRef::Literal(term) => Self::Literal(InternedLiteral::encoded_into(term, interner)), + TermRef::Triple(triple) => Self::Triple(Box::new(InternedTriple::encoded_into( + triple.as_ref(), + interner, + ))), } } - pub fn encoded_from(term: TermRef<'_>, interner: &Rodeo) -> Option { + pub fn encoded_from(term: TermRef<'_>, interner: &Interner) -> Option { Some(match term { TermRef::NamedNode(term) => { Self::NamedNode(InternedNamedNode::encoded_from(term, interner)?) @@ -303,14 +326,19 @@ impl InternedTerm { Self::BlankNode(InternedBlankNode::encoded_from(term, interner)?) } TermRef::Literal(term) => Self::Literal(InternedLiteral::encoded_from(term, interner)?), + TermRef::Triple(triple) => Self::Triple(Box::new(InternedTriple::encoded_from( + triple.as_ref(), + interner, + )?)), }) } - pub fn decode_from<'a>(&self, interner: &'a Rodeo) -> TermRef<'a> { + pub fn decode_from<'a>(&self, interner: &'a Interner) -> TermRef<'a> { match self { Self::NamedNode(term) => TermRef::NamedNode(term.decode_from(interner)), Self::BlankNode(term) => TermRef::BlankNode(term.decode_from(interner)), Self::Literal(term) => TermRef::Literal(term.decode_from(interner)), + Self::Triple(triple) => TermRef::Triple(&interner.triples[triple.as_ref()]), } } @@ -323,6 +351,7 @@ impl InternedTerm { Self::NamedNode(node) => Self::NamedNode(node.next()), Self::BlankNode(node) => Self::BlankNode(node.next()), Self::Literal(node) => Self::Literal(node.next()), + Self::Triple(triple) => Self::Triple(Box::new(triple.next())), } } @@ -331,6 +360,48 @@ impl InternedTerm { } } +#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] +pub struct InternedTriple { + pub subject: InternedSubject, + pub predicate: InternedNamedNode, + pub object: InternedTerm, +} + +impl InternedTriple { + pub fn encoded_into(triple: TripleRef<'_>, interner: &mut Interner) -> Self { + let interned_triple = Self { + subject: InternedSubject::encoded_into(triple.subject, interner), + predicate: InternedNamedNode::encoded_into(triple.predicate, interner), + object: InternedTerm::encoded_into(triple.object, interner), + }; + interner + .triples + .insert(interned_triple.clone(), triple.into_owned()); + interned_triple + } + + pub fn encoded_from(triple: TripleRef<'_>, interner: &Interner) -> Option { + let interned_triple = Self { + subject: InternedSubject::encoded_from(triple.subject, interner)?, + predicate: InternedNamedNode::encoded_from(triple.predicate, interner)?, + object: InternedTerm::encoded_from(triple.object, interner)?, + }; + if interner.triples.contains_key(&interned_triple) { + Some(interned_triple) + } else { + None + } + } + + pub fn next(&self) -> Self { + Self { + subject: self.subject.clone(), + predicate: self.predicate, + object: self.object.next(), + } + } +} + fn fist_spur() -> Spur { Spur::try_from_usize(0).unwrap() } diff --git a/lib/src/model/sophia.rs b/lib/src/model/sophia.rs index 135f1e16..e64d35b1 100644 --- a/lib/src/model/sophia.rs +++ b/lib/src/model/sophia.rs @@ -191,26 +191,26 @@ impl<'a> From> for Option> { impl TTerm for Subject { fn kind(&self) -> TermKind { - use Subject::*; match self { - NamedNode(_) => TermKind::Iri, - BlankNode(_) => TermKind::BlankNode, + Self::NamedNode(_) => TermKind::Iri, + Self::BlankNode(_) => TermKind::BlankNode, + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } fn value_raw(&self) -> RawValue<'_> { - use Subject::*; match self { - NamedNode(n) => n.value_raw(), - BlankNode(n) => n.value_raw(), + Self::NamedNode(n) => n.value_raw(), + Self::BlankNode(n) => n.value_raw(), + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } fn as_dyn(&self) -> &dyn TTerm { - use Subject::*; match self { - NamedNode(n) => n.as_dyn(), - BlankNode(n) => n.as_dyn(), + Self::NamedNode(n) => n.as_dyn(), + Self::BlankNode(n) => n.as_dyn(), + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } } @@ -232,71 +232,71 @@ impl TryCopyTerm for Subject { impl<'a> TTerm for SubjectRef<'a> { fn kind(&self) -> TermKind { - use SubjectRef::*; match self { - NamedNode(_) => TermKind::Iri, - BlankNode(_) => TermKind::BlankNode, + Self::NamedNode(_) => TermKind::Iri, + Self::BlankNode(_) => TermKind::BlankNode, + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } fn value_raw(&self) -> RawValue<'_> { - use SubjectRef::*; match self { - NamedNode(n) => n.value_raw(), - BlankNode(n) => n.value_raw(), + Self::NamedNode(n) => n.value_raw(), + Self::BlankNode(n) => n.value_raw(), + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } fn as_dyn(&self) -> &dyn TTerm { - use SubjectRef::*; match self { - NamedNode(n) => n.as_dyn(), - BlankNode(n) => n.as_dyn(), + Self::NamedNode(n) => n.as_dyn(), + Self::BlankNode(n) => n.as_dyn(), + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } } impl TTerm for Term { fn kind(&self) -> TermKind { - use Term::*; match self { - NamedNode(_) => TermKind::Iri, - BlankNode(_) => TermKind::BlankNode, - Literal(_) => TermKind::Literal, + Self::NamedNode(_) => TermKind::Iri, + Self::BlankNode(_) => TermKind::BlankNode, + Self::Literal(_) => TermKind::Literal, + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } fn value_raw(&self) -> RawValue<'_> { - use Term::*; match self { - NamedNode(n) => n.value_raw(), - BlankNode(n) => n.value_raw(), - Literal(l) => l.value_raw(), + Self::NamedNode(n) => n.value_raw(), + Self::BlankNode(n) => n.value_raw(), + Self::Literal(l) => l.value_raw(), + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } fn datatype(&self) -> Option> { - use Term::*; - match self { - Literal(l) => TTerm::datatype(l), - _ => None, + if let Self::Literal(l) = self { + TTerm::datatype(l) + } else { + None } } fn language(&self) -> Option<&str> { - use Term::*; - match self { - Literal(l) => TTerm::language(l), - _ => None, + if let Self::Literal(l) = self { + TTerm::language(l) + } else { + None } } fn as_dyn(&self) -> &dyn TTerm { - use Term::*; match self { - NamedNode(n) => n.as_dyn(), - BlankNode(n) => n.as_dyn(), - Literal(l) => l.as_dyn(), + Self::NamedNode(n) => n.as_dyn(), + Self::BlankNode(n) => n.as_dyn(), + Self::Literal(l) => l.as_dyn(), + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } } @@ -319,45 +319,45 @@ impl TryCopyTerm for Term { impl<'a> TTerm for TermRef<'a> { fn kind(&self) -> TermKind { - use TermRef::*; match self { - NamedNode(_) => TermKind::Iri, - BlankNode(_) => TermKind::BlankNode, - Literal(_) => TermKind::Literal, + Self::NamedNode(_) => TermKind::Iri, + Self::BlankNode(_) => TermKind::BlankNode, + Self::Literal(_) => TermKind::Literal, + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } fn value_raw(&self) -> RawValue<'_> { - use TermRef::*; match self { - NamedNode(n) => n.value_raw(), - BlankNode(n) => n.value_raw(), - Literal(l) => l.value_raw(), + Self::NamedNode(n) => n.value_raw(), + Self::BlankNode(n) => n.value_raw(), + Self::Literal(l) => l.value_raw(), + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } fn datatype(&self) -> Option> { - use TermRef::*; - match self { - Literal(l) => TTerm::datatype(l), - _ => None, + if let Self::Literal(l) = self { + TTerm::datatype(l) + } else { + None } } fn language(&self) -> Option<&str> { - use TermRef::*; - match self { - Literal(l) => TTerm::language(l), - _ => None, + if let Self::Literal(l) = self { + TTerm::language(l) + } else { + None } } fn as_dyn(&self) -> &dyn TTerm { - use TermRef::*; match self { - NamedNode(n) => n.as_dyn(), - BlankNode(n) => n.as_dyn(), - Literal(l) => l.as_dyn(), + Self::NamedNode(n) => n.as_dyn(), + Self::BlankNode(n) => n.as_dyn(), + Self::Literal(l) => l.as_dyn(), + Self::Triple(_) => panic!("RDF-star is not supported yet by Sophia"), } } } diff --git a/lib/src/model/triple.rs b/lib/src/model/triple.rs index 2908253a..d5a74a3d 100644 --- a/lib/src/model/triple.rs +++ b/lib/src/model/triple.rs @@ -4,12 +4,14 @@ use crate::model::named_node::NamedNode; use crate::model::{BlankNodeRef, LiteralRef, NamedNodeRef}; use rio_api::model as rio; use std::fmt; +use std::sync::Arc; -/// The owned union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) and [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node). +/// The owned union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri), [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) and [triples](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-triple). #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub enum Subject { NamedNode(NamedNode), BlankNode(BlankNode), + Triple(Arc), } impl Subject { @@ -23,11 +25,17 @@ impl Subject { self.as_ref().is_blank_node() } + #[inline] + pub fn is_triple(&self) -> bool { + self.as_ref().is_triple() + } + #[inline] pub fn as_ref(&self) -> SubjectRef<'_> { match self { Self::NamedNode(node) => SubjectRef::NamedNode(node.as_ref()), Self::BlankNode(node) => SubjectRef::BlankNode(node.as_ref()), + Self::Triple(triple) => SubjectRef::Triple(triple), } } } @@ -67,28 +75,42 @@ impl From> for Subject { } } -/// The borrowed union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) and [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node). +impl From for Subject { + #[inline] + fn from(node: Triple) -> Self { + Self::Triple(Arc::new(node)) + } +} + +impl From> for Subject { + #[inline] + fn from(node: TripleRef<'_>) -> Self { + node.into_owned().into() + } +} + +/// The borrowed union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri), [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) and [triples](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-triple). #[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] pub enum SubjectRef<'a> { NamedNode(NamedNodeRef<'a>), BlankNode(BlankNodeRef<'a>), + Triple(&'a Triple), } impl<'a> SubjectRef<'a> { #[inline] pub fn is_named_node(&self) -> bool { - match self { - Self::NamedNode(_) => true, - Self::BlankNode(_) => false, - } + matches!(self, Self::NamedNode(_)) } #[inline] pub fn is_blank_node(&self) -> bool { - match self { - Self::NamedNode(_) => false, - Self::BlankNode(_) => true, - } + matches!(self, Self::BlankNode(_)) + } + + #[inline] + pub fn is_triple(&self) -> bool { + matches!(self, Self::Triple(_)) } #[inline] @@ -96,6 +118,7 @@ impl<'a> SubjectRef<'a> { match self { Self::NamedNode(node) => Subject::NamedNode(node.into_owned()), Self::BlankNode(node) => Subject::BlankNode(node.into_owned()), + Self::Triple(triple) => Subject::Triple(Arc::new(triple.clone())), } } } @@ -106,6 +129,11 @@ impl fmt::Display for SubjectRef<'_> { match self { Self::NamedNode(node) => node.fmt(f), Self::BlankNode(node) => node.fmt(f), + Self::Triple(triple) => write!( + f, + "<< {} {} {} >>", + triple.subject, triple.predicate, triple.object + ), } } } @@ -138,6 +166,13 @@ impl<'a> From<&'a BlankNode> for SubjectRef<'a> { } } +impl<'a> From<&'a Triple> for SubjectRef<'a> { + #[inline] + fn from(node: &'a Triple) -> Self { + Self::Triple(node) + } +} + impl<'a> From<&'a Subject> for SubjectRef<'a> { #[inline] fn from(node: &'a Subject) -> Self { @@ -152,23 +187,26 @@ impl<'a> From> for Subject { } } +#[allow(clippy::unimplemented, clippy::fallible_impl_from)] impl<'a> From> for rio::NamedOrBlankNode<'a> { #[inline] fn from(node: SubjectRef<'a>) -> Self { match node { SubjectRef::NamedNode(node) => rio::NamedNode::from(node).into(), SubjectRef::BlankNode(node) => rio::BlankNode::from(node).into(), + SubjectRef::Triple(_) => unimplemented!("Rio library does not support RDF* yet"), } } } /// An owned RDF [term](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-term) -/// It is the union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri), [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) and [literals](https://www.w3.org/TR/rdf11-concepts/#dfn-literal). +/// It is the union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri), [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node), [literals](https://www.w3.org/TR/rdf11-concepts/#dfn-literal) and [triples](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-triple). #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub enum Term { NamedNode(NamedNode), BlankNode(BlankNode), Literal(Literal), + Triple(Arc), } impl Term { @@ -187,12 +225,18 @@ impl Term { self.as_ref().is_literal() } + #[inline] + pub fn is_triple(&self) -> bool { + self.as_ref().is_triple() + } + #[inline] pub fn as_ref(&self) -> TermRef<'_> { match self { Self::NamedNode(node) => TermRef::NamedNode(node.as_ref()), Self::BlankNode(node) => TermRef::BlankNode(node.as_ref()), Self::Literal(literal) => TermRef::Literal(literal.as_ref()), + Self::Triple(triple) => TermRef::Triple(triple), } } } @@ -245,6 +289,19 @@ impl From> for Term { literal.into_owned().into() } } +impl From for Term { + #[inline] + fn from(triple: Triple) -> Self { + Self::Triple(Arc::new(triple)) + } +} + +impl From> for Term { + #[inline] + fn from(triple: TripleRef<'_>) -> Self { + triple.into_owned().into() + } +} impl From for Term { #[inline] @@ -252,6 +309,7 @@ impl From for Term { match node { Subject::NamedNode(node) => node.into(), Subject::BlankNode(node) => node.into(), + Subject::Triple(triple) => Self::Triple(triple), } } } @@ -264,12 +322,13 @@ impl From> for Term { } /// A borrowed RDF [term](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-term) -/// It is the union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri), [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) and [literals](https://www.w3.org/TR/rdf11-concepts/#dfn-literal). +/// It is the union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri), [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node), [literals](https://www.w3.org/TR/rdf11-concepts/#dfn-literal) and [triples](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-triple). #[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] pub enum TermRef<'a> { NamedNode(NamedNodeRef<'a>), BlankNode(BlankNodeRef<'a>), Literal(LiteralRef<'a>), + Triple(&'a Triple), } impl<'a> TermRef<'a> { @@ -288,12 +347,18 @@ impl<'a> TermRef<'a> { matches!(self, Self::Literal(_)) } + #[inline] + pub fn is_triple(&self) -> bool { + matches!(self, Self::Triple(_)) + } + #[inline] pub fn into_owned(self) -> Term { match self { Self::NamedNode(node) => Term::NamedNode(node.into_owned()), Self::BlankNode(node) => Term::BlankNode(node.into_owned()), Self::Literal(literal) => Term::Literal(literal.into_owned()), + Self::Triple(triple) => Term::Triple(Arc::new(triple.clone())), } } } @@ -304,7 +369,14 @@ impl fmt::Display for TermRef<'_> { match self { Self::NamedNode(node) => node.fmt(f), Self::BlankNode(node) => node.fmt(f), - Self::Literal(node) => node.fmt(f), + Self::Literal(literal) => literal.fmt(f), + Self::Triple(triple) => { + write!( + f, + "<< {} {} {} >>", + triple.subject, triple.predicate, triple.object + ) + } } } } @@ -351,12 +423,20 @@ impl<'a> From<&'a Literal> for TermRef<'a> { } } +impl<'a> From<&'a Triple> for TermRef<'a> { + #[inline] + fn from(node: &'a Triple) -> Self { + Self::Triple(node) + } +} + impl<'a> From> for TermRef<'a> { #[inline] fn from(node: SubjectRef<'a>) -> Self { match node { SubjectRef::NamedNode(node) => node.into(), SubjectRef::BlankNode(node) => node.into(), + SubjectRef::Triple(triple) => triple.into(), } } } @@ -382,6 +462,7 @@ impl<'a> From> for Term { } } +#[allow(clippy::unimplemented, clippy::fallible_impl_from)] impl<'a> From> for rio::Term<'a> { #[inline] fn from(node: TermRef<'a>) -> Self { @@ -389,6 +470,7 @@ impl<'a> From> for rio::Term<'a> { TermRef::NamedNode(node) => rio::NamedNode::from(node).into(), TermRef::BlankNode(node) => rio::BlankNode::from(node).into(), TermRef::Literal(node) => rio::Literal::from(node).into(), + TermRef::Triple(_) => unimplemented!("Rio library does not support RDF* yet"), } } } @@ -600,45 +682,6 @@ impl From> for GraphName { } } -impl From for GraphName { - #[inline] - fn from(node: Subject) -> Self { - match node { - Subject::NamedNode(node) => node.into(), - Subject::BlankNode(node) => node.into(), - } - } -} - -impl From> for GraphName { - #[inline] - fn from(node: SubjectRef<'_>) -> Self { - node.into_owned().into() - } -} - -impl From> for GraphName { - #[inline] - fn from(name: Option) -> Self { - if let Some(node) = name { - node.into() - } else { - GraphName::DefaultGraph - } - } -} - -impl From for Option { - #[inline] - fn from(name: GraphName) -> Self { - match name { - GraphName::NamedNode(node) => Some(node.into()), - GraphName::BlankNode(node) => Some(node.into()), - GraphName::DefaultGraph => None, - } - } -} - /// A possible borrowed graph name. /// It is the union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri), [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node), and the [default graph name](https://www.w3.org/TR/rdf11-concepts/#dfn-default-graph). #[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] @@ -713,23 +756,6 @@ impl<'a> From<&'a BlankNode> for GraphNameRef<'a> { } } -impl<'a> From> for GraphNameRef<'a> { - #[inline] - fn from(node: SubjectRef<'a>) -> Self { - match node { - SubjectRef::NamedNode(node) => node.into(), - SubjectRef::BlankNode(node) => node.into(), - } - } -} - -impl<'a> From<&'a Subject> for GraphNameRef<'a> { - #[inline] - fn from(node: &'a Subject) -> Self { - node.as_ref().into() - } -} - impl<'a> From<&'a GraphName> for GraphNameRef<'a> { #[inline] fn from(node: &'a GraphName) -> Self { @@ -744,28 +770,6 @@ impl<'a> From> for GraphName { } } -impl<'a> From>> for GraphNameRef<'a> { - #[inline] - fn from(name: Option>) -> Self { - if let Some(node) = name { - node.into() - } else { - GraphNameRef::DefaultGraph - } - } -} - -impl<'a> From> for Option> { - #[inline] - fn from(name: GraphNameRef<'a>) -> Self { - match name { - GraphNameRef::NamedNode(node) => Some(node.into()), - GraphNameRef::BlankNode(node) => Some(node.into()), - GraphNameRef::DefaultGraph => None, - } - } -} - impl<'a> From> for Option> { #[inline] fn from(name: GraphNameRef<'a>) -> Self { diff --git a/lib/src/sparql/csv_results.rs b/lib/src/sparql/csv_results.rs index 5f082fb7..4baa0537 100644 --- a/lib/src/sparql/csv_results.rs +++ b/lib/src/sparql/csv_results.rs @@ -59,18 +59,25 @@ pub fn write_csv_results( Ok(()) } -fn write_csv_term<'a>(term: impl Into>, mut sink: impl Write) -> io::Result<()> { +fn write_csv_term<'a>(term: impl Into>, sink: &mut impl Write) -> io::Result<()> { match term.into() { TermRef::NamedNode(uri) => sink.write_all(uri.as_str().as_bytes()), TermRef::BlankNode(bnode) => { sink.write_all(b"_:")?; sink.write_all(bnode.as_str().as_bytes()) } - TermRef::Literal(literal) => write_escaped_csv_string(literal.value(), &mut sink), + TermRef::Literal(literal) => write_escaped_csv_string(literal.value(), sink), + TermRef::Triple(triple) => { + write_csv_term(&triple.subject, sink)?; + sink.write_all(b" ")?; + write_csv_term(&triple.predicate, sink)?; + sink.write_all(b" ")?; + write_csv_term(&triple.object, sink) + } } } -fn write_escaped_csv_string(s: &str, mut sink: impl Write) -> io::Result<()> { +fn write_escaped_csv_string(s: &str, sink: &mut impl Write) -> io::Result<()> { if s.bytes().any(|c| matches!(c, b'"' | b',' | b'\n' | b'\r')) { sink.write_all(b"\"")?; for c in s.bytes() { @@ -138,7 +145,7 @@ pub fn write_tsv_results( Ok(()) } -fn write_tsv_term<'a>(term: impl Into>, mut sink: impl Write) -> io::Result<()> { +fn write_tsv_term<'a>(term: impl Into>, sink: &mut impl Write) -> io::Result<()> { //TODO: full Turtle serialization match term.into() { TermRef::NamedNode(node) => write!(sink, "<{}>", node.as_str()), @@ -158,6 +165,16 @@ fn write_tsv_term<'a>(term: impl Into>, mut sink: impl Write) -> io: } _ => sink.write_all(literal.to_string().as_bytes()), }, + TermRef::Triple(triple) => { + sink.write_all(b"<< ")?; + write_tsv_term(&triple.subject, sink)?; + sink.write_all(b" ")?; + write_tsv_term(&triple.predicate, sink)?; + sink.write_all(b" ")?; + write_tsv_term(&triple.object, sink)?; + sink.write_all(b" >>")?; + Ok(()) + } } } diff --git a/lib/src/sparql/eval.rs b/lib/src/sparql/eval.rs index 5098d292..8ec2e289 100644 --- a/lib/src/sparql/eval.rs +++ b/lib/src/sparql/eval.rs @@ -1590,7 +1590,8 @@ impl SimpleEvaluator { EncodedTerm::NamedNode { iri_id } => Some((*iri_id).into()), EncodedTerm::NumericalBlankNode { .. } | EncodedTerm::SmallBlankNode { .. } - | EncodedTerm::BigBlankNode { .. } => None, + | EncodedTerm::BigBlankNode { .. } + | EncodedTerm::Triple(_) => None, EncodedTerm::SmallStringLiteral(value) | EncodedTerm::SmallSmallLangStringLiteral { value, .. } | EncodedTerm::SmallBigLangStringLiteral { value, .. } @@ -2005,6 +2006,17 @@ impl SimpleEvaluator { _ if b.is_unknown_typed_literal() => None, _ => Some(false), }, + EncodedTerm::Triple(a) => { + if let EncodedTerm::Triple(b) = b { + Some( + self.equals(&a.subject, &b.subject)? + && self.equals(&a.predicate, &b.predicate)? + && self.equals(&a.object, &b.object)?, + ) + } else { + Some(false) + } + } } } @@ -2194,7 +2206,8 @@ impl SimpleEvaluator { | EncodedTerm::SmallBlankNode { .. } | EncodedTerm::BigBlankNode { .. } | EncodedTerm::NumericalBlankNode { .. } - | EncodedTerm::DefaultGraph => None, + | EncodedTerm::DefaultGraph + | EncodedTerm::Triple(_) => None, EncodedTerm::SmallStringLiteral(_) | EncodedTerm::BigStringLiteral { .. } => { self.build_named_node(xsd::STRING.as_str()) } diff --git a/lib/src/sparql/json_results.rs b/lib/src/sparql/json_results.rs index 609aa888..f9fa4a79 100644 --- a/lib/src/sparql/json_results.rs +++ b/lib/src/sparql/json_results.rs @@ -47,30 +47,8 @@ pub fn write_json_results( sink.write_all(b",")?; } write_escaped_json_string(variable.as_str(), &mut sink)?; - match value { - Term::NamedNode(uri) => { - sink.write_all(b":{\"type\":\"uri\",\"value\":")?; - write_escaped_json_string(uri.as_str(), &mut sink)?; - sink.write_all(b"}")?; - } - Term::BlankNode(bnode) => { - sink.write_all(b":{\"type\":\"bnode\",\"value\":")?; - write_escaped_json_string(bnode.as_str(), &mut sink)?; - sink.write_all(b"}")?; - } - Term::Literal(literal) => { - sink.write_all(b":{\"type\":\"literal\",\"value\":")?; - write_escaped_json_string(literal.value(), &mut sink)?; - if let Some(language) = literal.language() { - sink.write_all(b",\"xml:lang\":")?; - write_escaped_json_string(language, &mut sink)?; - } else if !literal.is_plain() { - sink.write_all(b",\"datatype\":")?; - write_escaped_json_string(literal.datatype().as_str(), &mut sink)?; - } - sink.write_all(b"}")?; - } - } + sink.write_all(b":")?; + write_json_term(value.as_ref(), &mut sink)?; } sink.write_all(b"}")?; } @@ -84,7 +62,44 @@ pub fn write_json_results( } } -fn write_escaped_json_string(s: &str, mut sink: impl Write) -> Result<(), EvaluationError> { +fn write_json_term(term: TermRef<'_>, sink: &mut impl Write) -> Result<(), EvaluationError> { + match term { + TermRef::NamedNode(uri) => { + sink.write_all(b"{\"type\":\"uri\",\"value\":")?; + write_escaped_json_string(uri.as_str(), sink)?; + sink.write_all(b"}")?; + } + TermRef::BlankNode(bnode) => { + sink.write_all(b"{\"type\":\"bnode\",\"value\":")?; + write_escaped_json_string(bnode.as_str(), sink)?; + sink.write_all(b"}")?; + } + TermRef::Literal(literal) => { + sink.write_all(b"{\"type\":\"literal\",\"value\":")?; + write_escaped_json_string(literal.value(), sink)?; + if let Some(language) = literal.language() { + sink.write_all(b",\"xml:lang\":")?; + write_escaped_json_string(language, sink)?; + } else if !literal.is_plain() { + sink.write_all(b",\"datatype\":")?; + write_escaped_json_string(literal.datatype().as_str(), sink)?; + } + sink.write_all(b"}")?; + } + TermRef::Triple(triple) => { + sink.write_all(b":{\"type\":\"triple\",\"value\":{\"subject\":")?; + write_json_term(triple.subject.as_ref().into(), sink)?; + sink.write_all(b":,\"predicate\":")?; + write_json_term(triple.predicate.as_ref().into(), sink)?; + sink.write_all(b":,\"object\":")?; + write_json_term(triple.object.as_ref(), sink)?; + sink.write_all(b"}}")?; + } + } + Ok(()) +} + +fn write_escaped_json_string(s: &str, sink: &mut impl Write) -> Result<(), EvaluationError> { sink.write_all(b"\"")?; for c in s.chars() { match c { diff --git a/lib/src/sparql/update.rs b/lib/src/sparql/update.rs index cd19b98d..60c2b290 100644 --- a/lib/src/sparql/update.rs +++ b/lib/src/sparql/update.rs @@ -129,7 +129,7 @@ impl<'a> SimpleUpdateEvaluator<'a> { .into_iter() .map(|t| { Ok(if let Some(t) = t { - let r: Result<_, EvaluationError> = t.on_each_id(|id| { + let r: Result<_, EvaluationError> = t.on_each_id(&mut |id| { self.storage.insert_str( id, &dataset.get_str(id)?.ok_or_else(|| { diff --git a/lib/src/sparql/xml_results.rs b/lib/src/sparql/xml_results.rs index 88a55d5f..c94c397e 100644 --- a/lib/src/sparql/xml_results.rs +++ b/lib/src/sparql/xml_results.rs @@ -88,47 +88,7 @@ fn write_solutions(solutions: QuerySolutionIter, sink: impl Write) -> Result<(), writer .write_event(Event::Start(binding_tag)) .map_err(map_xml_error)?; - match value { - Term::NamedNode(uri) => { - writer - .write_event(Event::Start(BytesStart::borrowed_name(b"uri"))) - .map_err(map_xml_error)?; - writer - .write_event(Event::Text(BytesText::from_plain_str(uri.as_str()))) - .map_err(map_xml_error)?; - writer - .write_event(Event::End(BytesEnd::borrowed(b"uri"))) - .map_err(map_xml_error)?; - } - Term::BlankNode(bnode) => { - writer - .write_event(Event::Start(BytesStart::borrowed_name(b"bnode"))) - .map_err(map_xml_error)?; - writer - .write_event(Event::Text(BytesText::from_plain_str(bnode.as_str()))) - .map_err(map_xml_error)?; - writer - .write_event(Event::End(BytesEnd::borrowed(b"bnode"))) - .map_err(map_xml_error)?; - } - Term::Literal(literal) => { - let mut literal_tag = BytesStart::borrowed_name(b"literal"); - if let Some(language) = literal.language() { - literal_tag.push_attribute(("xml:lang", language)); - } else if !literal.is_plain() { - literal_tag.push_attribute(("datatype", literal.datatype().as_str())); - } - writer - .write_event(Event::Start(literal_tag)) - .map_err(map_xml_error)?; - writer - .write_event(Event::Text(BytesText::from_plain_str(literal.value()))) - .map_err(map_xml_error)?; - writer - .write_event(Event::End(BytesEnd::borrowed(b"literal"))) - .map_err(map_xml_error)?; - } - } + write_xml_term(value.as_ref(), &mut writer)?; writer .write_event(Event::End(BytesEnd::borrowed(b"binding"))) .map_err(map_xml_error)?; @@ -146,6 +106,83 @@ fn write_solutions(solutions: QuerySolutionIter, sink: impl Write) -> Result<(), Ok(()) } +fn write_xml_term( + term: TermRef<'_>, + writer: &mut Writer, +) -> Result<(), EvaluationError> { + match term { + TermRef::NamedNode(uri) => { + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"uri"))) + .map_err(map_xml_error)?; + writer + .write_event(Event::Text(BytesText::from_plain_str(uri.as_str()))) + .map_err(map_xml_error)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"uri"))) + .map_err(map_xml_error)?; + } + TermRef::BlankNode(bnode) => { + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"bnode"))) + .map_err(map_xml_error)?; + writer + .write_event(Event::Text(BytesText::from_plain_str(bnode.as_str()))) + .map_err(map_xml_error)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"bnode"))) + .map_err(map_xml_error)?; + } + TermRef::Literal(literal) => { + let mut literal_tag = BytesStart::borrowed_name(b"literal"); + if let Some(language) = literal.language() { + literal_tag.push_attribute(("xml:lang", language)); + } else if !literal.is_plain() { + literal_tag.push_attribute(("datatype", literal.datatype().as_str())); + } + writer + .write_event(Event::Start(literal_tag)) + .map_err(map_xml_error)?; + writer + .write_event(Event::Text(BytesText::from_plain_str(literal.value()))) + .map_err(map_xml_error)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"literal"))) + .map_err(map_xml_error)?; + } + TermRef::Triple(triple) => { + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"triple"))) + .map_err(map_xml_error)?; + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"subject"))) + .map_err(map_xml_error)?; + write_xml_term(triple.subject.as_ref().into(), writer)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"subject"))) + .map_err(map_xml_error)?; + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"predicate"))) + .map_err(map_xml_error)?; + write_xml_term(triple.predicate.as_ref().into(), writer)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"predicate"))) + .map_err(map_xml_error)?; + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"object"))) + .map_err(map_xml_error)?; + write_xml_term(triple.object.as_ref(), writer)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"object"))) + .map_err(map_xml_error)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"triple"))) + .map_err(map_xml_error)?; + } + } + Ok(()) +} + pub fn read_xml_results(source: impl BufRead + 'static) -> Result { enum State { Start, diff --git a/lib/src/storage/binary_encoder.rs b/lib/src/storage/binary_encoder.rs index 5882b7b4..04cd89fd 100644 --- a/lib/src/storage/binary_encoder.rs +++ b/lib/src/storage/binary_encoder.rs @@ -1,10 +1,11 @@ use crate::error::invalid_data_error; use crate::model::xsd::*; -use crate::storage::numeric_encoder::{EncodedQuad, EncodedTerm, StrHash}; +use crate::storage::numeric_encoder::{EncodedQuad, EncodedTerm, EncodedTriple, StrHash}; use crate::storage::small_string::SmallString; use std::io; use std::io::{Cursor, Read}; use std::mem::size_of; +use std::rc::Rc; pub const LATEST_STORAGE_VERSION: u64 = 1; pub const WRITTEN_TERM_MAX_SIZE: usize = size_of::() + 2 * size_of::(); @@ -13,7 +14,8 @@ pub const WRITTEN_TERM_MAX_SIZE: usize = size_of::() + 2 * size_of:: TermReader for R { DayTimeDuration::from_be_bytes(buffer), )) } + TYPE_TRIPLE => Ok(EncodedTerm::Triple(Rc::new(EncodedTriple { + subject: self.read_term()?, + predicate: self.read_term()?, + object: self.read_term()?, + }))), _ => Err(invalid_data_error("the term buffer has an invalid type id")), } } @@ -621,6 +629,12 @@ pub fn write_term(sink: &mut Vec, term: &EncodedTerm) { sink.push(TYPE_DAY_TIME_DURATION_LITERAL); sink.extend_from_slice(&value.to_be_bytes()) } + EncodedTerm::Triple(value) => { + sink.push(TYPE_TRIPLE); + write_term(sink, &value.subject); + write_term(sink, &value.predicate); + write_term(sink, &value.object); + } } } @@ -715,6 +729,12 @@ mod tests { NamedNode::new_unchecked("http://foo.com"), ) .into(), + Triple::new( + NamedNode::new_unchecked("http://foo.com"), + NamedNode::new_unchecked("http://bar.com"), + Literal::from(true), + ) + .into(), ]; for term in terms { let encoded = store.encode_term(term.as_ref()).unwrap(); diff --git a/lib/src/storage/numeric_encoder.rs b/lib/src/storage/numeric_encoder.rs index 94fe66a4..049b2072 100644 --- a/lib/src/storage/numeric_encoder.rs +++ b/lib/src/storage/numeric_encoder.rs @@ -14,6 +14,7 @@ use std::error::Error; use std::fmt::Debug; use std::hash::Hash; use std::hash::Hasher; +use std::rc::Rc; use std::{fmt, io, str}; #[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] @@ -101,6 +102,7 @@ pub enum EncodedTerm { DurationLiteral(Duration), YearMonthDurationLiteral(YearMonthDuration), DayTimeDurationLiteral(DayTimeDuration), + Triple(Rc), } impl PartialEq for EncodedTerm { @@ -214,6 +216,7 @@ impl PartialEq for EncodedTerm { (Self::DurationLiteral(a), Self::DurationLiteral(b)) => a == b, (Self::YearMonthDurationLiteral(a), Self::YearMonthDurationLiteral(b)) => a == b, (Self::DayTimeDurationLiteral(a), Self::DayTimeDurationLiteral(b)) => a == b, + (Self::Triple(a), Self::Triple(b)) => a == b, (_, _) => false, } } @@ -277,6 +280,7 @@ impl Hash for EncodedTerm { Self::DurationLiteral(value) => value.hash(state), Self::YearMonthDurationLiteral(value) => value.hash(state), Self::DayTimeDurationLiteral(value) => value.hash(state), + Self::Triple(value) => value.hash(state), } } } @@ -338,7 +342,7 @@ impl EncodedTerm { pub fn on_each_id( &self, - mut callback: impl FnMut(&StrHash) -> Result<(), E>, + callback: &mut impl FnMut(&StrHash) -> Result<(), E>, ) -> Result<(), E> { match self { Self::NamedNode { iri_id } => { @@ -373,6 +377,11 @@ impl EncodedTerm { callback(value_id)?; callback(datatype_id)?; } + Self::Triple(triple) => { + triple.subject.on_each_id(callback)?; + triple.predicate.on_each_id(callback)?; + triple.object.on_each_id(callback)?; + } _ => (), } Ok(()) @@ -462,6 +471,19 @@ impl From for EncodedTerm { } } +impl From for EncodedTerm { + fn from(value: EncodedTriple) -> Self { + Self::Triple(Rc::new(value)) + } +} + +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub struct EncodedTriple { + pub subject: EncodedTerm, + pub predicate: EncodedTerm, + pub object: EncodedTerm, +} + #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct EncodedQuad { pub subject: EncodedTerm, @@ -613,6 +635,9 @@ pub(crate) fn get_encoded_subject(term: SubjectRef<'_>) -> EncodedTerm { match term { SubjectRef::NamedNode(named_node) => get_encoded_named_node(named_node), SubjectRef::BlankNode(blank_node) => get_encoded_blank_node(blank_node), + SubjectRef::Triple(triple) => { + EncodedTerm::Triple(Rc::new(get_encoded_triple(triple.as_ref()))) + } } } @@ -621,6 +646,9 @@ pub(crate) fn get_encoded_term(term: TermRef<'_>) -> EncodedTerm { TermRef::NamedNode(named_node) => get_encoded_named_node(named_node), TermRef::BlankNode(blank_node) => get_encoded_blank_node(blank_node), TermRef::Literal(literal) => get_encoded_literal(literal), + TermRef::Triple(triple) => { + EncodedTerm::Triple(Rc::new(get_encoded_triple(triple.as_ref()))) + } } } @@ -632,6 +660,14 @@ pub(crate) fn get_encoded_graph_name(name: GraphNameRef<'_>) -> EncodedTerm { } } +pub(crate) fn get_encoded_triple(quad: TripleRef<'_>) -> EncodedTriple { + EncodedTriple { + subject: get_encoded_subject(quad.subject), + predicate: get_encoded_named_node(quad.predicate), + object: get_encoded_term(quad.object), + } +} + pub(crate) fn get_encoded_quad(quad: QuadRef<'_>) -> EncodedQuad { EncodedQuad { subject: get_encoded_subject(quad.subject), @@ -670,6 +706,9 @@ pub(crate) trait WriteEncoder: StrContainer { match term { SubjectRef::NamedNode(named_node) => self.encode_named_node(named_node), SubjectRef::BlankNode(blank_node) => self.encode_blank_node(blank_node), + SubjectRef::Triple(triple) => Ok(EncodedTerm::Triple(Rc::new( + self.encode_triple(triple.as_ref())?, + ))), } } @@ -678,6 +717,9 @@ pub(crate) trait WriteEncoder: StrContainer { TermRef::NamedNode(named_node) => self.encode_named_node(named_node), TermRef::BlankNode(blank_node) => self.encode_blank_node(blank_node), TermRef::Literal(literal) => self.encode_literal(literal), + TermRef::Triple(triple) => Ok(EncodedTerm::Triple(Rc::new( + self.encode_triple(triple.as_ref())?, + ))), } } @@ -689,6 +731,14 @@ pub(crate) trait WriteEncoder: StrContainer { } } + fn encode_triple(&self, quad: TripleRef<'_>) -> Result { + Ok(EncodedTriple { + subject: self.encode_subject(quad.subject)?, + predicate: self.encode_named_node(quad.predicate)?, + object: self.encode_term(quad.object)?, + }) + } + fn encode_quad(&self, quad: QuadRef<'_>) -> Result { Ok(EncodedQuad { subject: self.encode_subject(quad.subject)?, @@ -980,7 +1030,10 @@ pub(crate) trait Decoder: StrLookup { Term::NamedNode(named_node) => Ok(named_node.into()), Term::BlankNode(blank_node) => Ok(blank_node.into()), Term::Literal(_) => Err(DecoderError::Decoder { - msg: "A literal has ben found instead of a named node".to_owned(), + msg: "A literal has been found instead of a named node".to_owned(), + }), + Term::Triple(_) => Err(DecoderError::Decoder { + msg: "A triple has been found instead of a named node".to_owned(), }), } } @@ -995,19 +1048,44 @@ pub(crate) trait Decoder: StrLookup { msg: "A blank node has been found instead of a named node".to_owned(), }), Term::Literal(_) => Err(DecoderError::Decoder { - msg: "A literal has ben found instead of a named node".to_owned(), + msg: "A literal has been found instead of a named node".to_owned(), + }), + Term::Triple(_) => Err(DecoderError::Decoder { + msg: "A triple has been found instead of a named node".to_owned(), }), } } + fn decode_triple(&self, encoded: &EncodedTriple) -> Result> { + Ok(Triple::new( + self.decode_subject(&encoded.subject)?, + self.decode_named_node(&encoded.predicate)?, + self.decode_term(&encoded.object)?, + )) + } + fn decode_quad(&self, encoded: &EncodedQuad) -> Result> { Ok(Quad::new( self.decode_subject(&encoded.subject)?, self.decode_named_node(&encoded.predicate)?, self.decode_term(&encoded.object)?, - match &encoded.graph_name { - EncodedTerm::DefaultGraph => None, - graph_name => Some(self.decode_subject(graph_name)?), + if encoded.graph_name == EncodedTerm::DefaultGraph { + GraphName::DefaultGraph + } else { + match self.decode_term(&encoded.graph_name)? { + Term::NamedNode(named_node) => named_node.into(), + Term::BlankNode(blank_node) => blank_node.into(), + Term::Literal(_) => { + return Err(DecoderError::Decoder { + msg: "A literal is not a valid graph name".to_owned(), + }) + } + Term::Triple(_) => { + return Err(DecoderError::Decoder { + msg: "A triple is not a valid graph name".to_owned(), + }) + } + } }, )) } @@ -1089,6 +1167,7 @@ impl Decoder for S { EncodedTerm::DurationLiteral(value) => Ok(Literal::from(*value).into()), EncodedTerm::YearMonthDurationLiteral(value) => Ok(Literal::from(*value).into()), EncodedTerm::DayTimeDurationLiteral(value) => Ok(Literal::from(*value).into()), + EncodedTerm::Triple(triple) => Ok(self.decode_triple(triple)?.into()), } } } diff --git a/lib/src/store.rs b/lib/src/store.rs index 124c1a0c..7ed67112 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -5,14 +5,12 @@ //! use oxigraph::store::Store; //! use oxigraph::sparql::QueryResults; //! use oxigraph::model::*; -//! # use std::fs::remove_dir_all; //! -//! # { -//! let store = Store::open("example.db")?; +//! let store = Store::new()?; //! //! // insertion //! let ex = NamedNode::new("http://example.com")?; -//! let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None); +//! let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), GraphName::DefaultGraph); //! store.insert(&quad)?; //! //! // quad filter @@ -23,9 +21,6 @@ //! if let QueryResults::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }")? { //! assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); //! }; -//! # -//! # }; -//! # remove_dir_all("example.db")?; //! # Result::<_,Box>::Ok(()) //! ``` @@ -69,7 +64,7 @@ use std::{fmt, io, str}; /// /// // insertion /// let ex = NamedNode::new("http://example.com")?; -/// let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None); +/// let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), GraphName::DefaultGraph); /// store.insert(&quad)?; /// /// // quad filter @@ -119,7 +114,7 @@ impl Store { /// /// // insertions /// let ex = NamedNodeRef::new("http://example.com")?; - /// store.insert(QuadRef::new(ex, ex, ex, None))?; + /// store.insert(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?; /// /// // SPARQL query /// if let QueryResults::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }")? { @@ -154,7 +149,7 @@ impl Store { /// /// // insertion /// let ex = NamedNode::new("http://example.com")?; - /// let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None); + /// let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), GraphName::DefaultGraph); /// store.insert(&quad)?; /// /// // quad filter by object @@ -220,7 +215,7 @@ impl Store { /// /// // we inspect the store contents /// let ex = NamedNodeRef::new("http://example.com").unwrap(); - /// assert!(store.contains(QuadRef::new(ex, ex, ex, None))?); + /// assert!(store.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?); /// # Result::<_,Box>::Ok(()) /// ``` pub fn update( @@ -298,7 +293,7 @@ impl Store { /// /// // we inspect the store contents /// let ex = NamedNodeRef::new("http://example.com")?; - /// assert!(store.contains(QuadRef::new(ex, ex, ex, None))?); + /// assert!(store.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?); /// # Result::<_,Box>::Ok(()) /// ``` /// @@ -441,12 +436,12 @@ impl Store { /// Usage example: /// ``` /// use oxigraph::store::Store; - /// use oxigraph::model::{NamedNode, QuadRef, Subject}; + /// use oxigraph::model::*; /// /// let ex = NamedNode::new("http://example.com")?; /// let store = Store::new()?; /// store.insert(QuadRef::new(&ex, &ex, &ex, &ex))?; - /// store.insert(QuadRef::new(&ex, &ex, &ex, None))?; + /// store.insert(QuadRef::new(&ex, &ex, &ex, GraphNameRef::DefaultGraph))?; /// assert_eq!(vec![Subject::from(ex)], store.named_graphs().collect::,_>>()?); /// # Result::<_,Box>::Ok(()) /// ``` @@ -560,12 +555,12 @@ impl Store { /// Usage example: /// ``` /// use oxigraph::store::Store; - /// use oxigraph::model::{NamedNodeRef, QuadRef}; + /// use oxigraph::model::*; /// /// let ex = NamedNodeRef::new("http://example.com")?; /// let store = Store::new()?; /// store.insert(QuadRef::new(ex, ex, ex, ex))?; - /// store.insert(QuadRef::new(ex, ex, ex, None))?; + /// store.insert(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?; /// assert_eq!(2, store.len()); /// /// store.clear()?; @@ -616,7 +611,7 @@ impl Transaction<'_> { /// /// // we inspect the store content /// let ex = NamedNodeRef::new("http://example.com")?; - /// assert!(store.contains(QuadRef::new(ex, ex, ex, None))?); + /// assert!(store.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?); /// # Result::<_, Box>::Ok(()) /// ``` /// @@ -769,7 +764,12 @@ fn store() -> Result<(), io::Error> { let main_o = Term::from(Literal::from(1)); let main_g = GraphName::from(BlankNode::default()); - let default_quad = Quad::new(main_s.clone(), main_p.clone(), main_o.clone(), None); + let default_quad = Quad::new( + main_s.clone(), + main_p.clone(), + main_o.clone(), + GraphName::DefaultGraph, + ); let named_quad = Quad::new( main_s.clone(), main_p.clone(), @@ -777,23 +777,33 @@ fn store() -> Result<(), io::Error> { main_g.clone(), ); let default_quads = vec![ - Quad::new(main_s.clone(), main_p.clone(), Literal::from(0), None), + Quad::new( + main_s.clone(), + main_p.clone(), + Literal::from(0), + GraphName::DefaultGraph, + ), default_quad.clone(), Quad::new( main_s.clone(), main_p.clone(), Literal::from(200000000), - None, + GraphName::DefaultGraph, ), ]; let all_quads = vec![ - Quad::new(main_s.clone(), main_p.clone(), Literal::from(0), None), + Quad::new( + main_s.clone(), + main_p.clone(), + Literal::from(0), + GraphName::DefaultGraph, + ), default_quad.clone(), Quad::new( main_s.clone(), main_p.clone(), Literal::from(200000000), - None, + GraphName::DefaultGraph, ), named_quad.clone(), ]; diff --git a/lib/tests/store.rs b/lib/tests/store.rs index 675bc479..d1dbf814 100644 --- a/lib/tests/store.rs +++ b/lib/tests/store.rs @@ -78,7 +78,12 @@ fn quads(graph_name: impl Into>) -> Vec> #[test] fn test_load_graph() -> io::Result<()> { let store = Store::new()?; - store.load_graph(Cursor::new(DATA), GraphFormat::Turtle, None, None)?; + store.load_graph( + Cursor::new(DATA), + GraphFormat::Turtle, + GraphNameRef::DefaultGraph, + None, + )?; for q in quads(GraphNameRef::DefaultGraph) { assert!(store.contains(q)?); } @@ -103,7 +108,11 @@ fn test_dump_graph() -> io::Result<()> { } let mut buffer = Vec::new(); - store.dump_graph(&mut buffer, GraphFormat::NTriples, None)?; + store.dump_graph( + &mut buffer, + GraphFormat::NTriples, + GraphNameRef::DefaultGraph, + )?; assert_eq!( buffer.into_iter().filter(|c| *c == b'\n').count(), NUMBER_OF_TRIPLES @@ -131,7 +140,12 @@ fn test_dump_dataset() -> io::Result<()> { fn test_transaction_load_graph() -> io::Result<()> { let store = Store::new()?; store.transaction(|t| { - t.load_graph(Cursor::new(DATA), GraphFormat::Turtle, None, None)?; + t.load_graph( + Cursor::new(DATA), + GraphFormat::Turtle, + GraphNameRef::DefaultGraph, + None, + )?; Ok(()) as Result<_, ConflictableTransactionError> })?; for q in quads(GraphNameRef::DefaultGraph) { diff --git a/python/src/model.rs b/python/src/model.rs index 02f33ae5..02bde29b 100644 --- a/python/src/model.rs +++ b/python/src/model.rs @@ -408,6 +408,7 @@ impl PyObjectProtocol for PyDefaultGraph { pub enum PySubject { NamedNode(PyNamedNode), BlankNode(PyBlankNode), + Triple(PyTriple), } impl From for Subject { @@ -415,6 +416,7 @@ impl From for Subject { match node { PySubject::NamedNode(node) => node.into(), PySubject::BlankNode(node) => node.into(), + PySubject::Triple(triple) => triple.into(), } } } @@ -424,6 +426,7 @@ impl From for PySubject { match node { Subject::NamedNode(node) => PySubject::NamedNode(node.into()), Subject::BlankNode(node) => PySubject::BlankNode(node.into()), + Subject::Triple(triple) => PySubject::Triple(triple.as_ref().clone().into()), } } } @@ -433,6 +436,7 @@ impl IntoPy for PySubject { match self { PySubject::NamedNode(node) => node.into_py(py), PySubject::BlankNode(node) => node.into_py(py), + PySubject::Triple(triple) => triple.into_py(py), } } } @@ -442,6 +446,7 @@ pub enum PyTerm { NamedNode(PyNamedNode), BlankNode(PyBlankNode), Literal(PyLiteral), + Triple(PyTriple), } impl From for Term { @@ -450,6 +455,7 @@ impl From for Term { PyTerm::NamedNode(node) => node.into(), PyTerm::BlankNode(node) => node.into(), PyTerm::Literal(literal) => literal.into(), + PyTerm::Triple(triple) => triple.into(), } } } @@ -460,6 +466,7 @@ impl From for PyTerm { Term::NamedNode(node) => PyTerm::NamedNode(node.into()), Term::BlankNode(node) => PyTerm::BlankNode(node.into()), Term::Literal(literal) => PyTerm::Literal(literal.into()), + Term::Triple(triple) => PyTerm::Triple(triple.as_ref().clone().into()), } } } @@ -470,6 +477,7 @@ impl IntoPy for PyTerm { PyTerm::NamedNode(node) => node.into_py(py), PyTerm::BlankNode(node) => node.into_py(py), PyTerm::Literal(literal) => literal.into_py(py), + PyTerm::Triple(triple) => triple.into_py(py), } } } @@ -505,14 +513,26 @@ impl From for PyTriple { } impl From for Triple { - fn from(node: PyTriple) -> Self { - node.inner + fn from(triple: PyTriple) -> Self { + triple.inner } } impl<'a> From<&'a PyTriple> for TripleRef<'a> { - fn from(node: &'a PyTriple) -> Self { - node.inner.as_ref() + fn from(triple: &'a PyTriple) -> Self { + triple.inner.as_ref() + } +} + +impl From for Subject { + fn from(triple: PyTriple) -> Self { + triple.inner.into() + } +} + +impl From for Term { + fn from(triple: PyTriple) -> Self { + triple.inner.into() } } @@ -562,13 +582,7 @@ impl PyObjectProtocol for PyTriple { fn __repr__(&self) -> String { let mut buffer = String::new(); - buffer.push_str(", diff --git a/python/tests/test_model.py b/python/tests/test_model.py index c878f2a5..b1e1e0b4 100644 --- a/python/tests/test_model.py +++ b/python/tests/test_model.py @@ -76,6 +76,31 @@ class TestTriple(unittest.TestCase): self.assertEqual(t.predicate, NamedNode("http://example.com/p")) self.assertEqual(t.object, NamedNode("http://example.com/o")) + def test_rdf_star_constructor(self): + t = Triple( + Triple( + NamedNode("http://example.com/ss"), + NamedNode("http://example.com/sp"), + NamedNode("http://example.com/so") + ), + NamedNode("http://example.com/p"), + Triple( + NamedNode("http://example.com/os"), + NamedNode("http://example.com/op"), + NamedNode("http://example.com/oo") + ), ) + self.assertEqual(t.subject, Triple( + NamedNode("http://example.com/ss"), + NamedNode("http://example.com/sp"), + NamedNode("http://example.com/so") + )) + self.assertEqual(t.predicate, NamedNode("http://example.com/p")) + self.assertEqual(t.object, Triple( + NamedNode("http://example.com/os"), + NamedNode("http://example.com/op"), + NamedNode("http://example.com/oo") + )) + def test_mapping(self): t = Triple( NamedNode("http://example.com/s"), diff --git a/python/tests/test_store.py b/python/tests/test_store.py index ecbf4e56..21852817 100644 --- a/python/tests/test_store.py +++ b/python/tests/test_store.py @@ -6,6 +6,7 @@ from pyoxigraph import * foo = NamedNode("http://foo") bar = NamedNode("http://bar") baz = NamedNode("http://baz") +triple = Triple(foo, foo, foo) graph = NamedNode("http://graph") @@ -15,7 +16,9 @@ class TestStore(unittest.TestCase): store.add(Quad(foo, bar, baz)) store.add(Quad(foo, bar, baz, DefaultGraph())) store.add(Quad(foo, bar, baz, graph)) - self.assertEqual(len(store), 2) + store.add(Quad(triple, bar, baz)) + store.add(Quad(foo, bar, triple)) + self.assertEqual(len(store), 4) def test_remove(self): store = Store()