Adds Repository::load_graph to load graph files

pull/10/head
Tpt 5 years ago
parent a803daa64b
commit 9ccc079b4c
  1. 4
      lib/src/lib.rs
  2. 1
      lib/src/model/graph.rs
  3. 1
      lib/src/model/named_node.rs
  4. 42
      lib/src/repository.rs
  5. 101
      lib/src/rio.rs
  6. 4
      lib/src/sparql/algebra.rs
  7. 6
      lib/src/sparql/eval.rs
  8. 1
      lib/src/store/memory.rs
  9. 73
      lib/src/store/mod.rs
  10. 118
      lib/src/store/numeric_encoder.rs
  11. 1
      lib/src/store/rocksdb.rs
  12. 368
      lib/tests/rdf_test_cases.rs
  13. 62
      lib/tests/sparql_test_cases.rs
  14. 14
      server/src/main.rs

@ -12,7 +12,6 @@
//! use rudf::model::*;
//! use rudf::{Repository, RepositoryConnection, MemoryRepository, Result};
//! use crate::rudf::sparql::PreparedQuery;
//! use std::str::FromStr;
//! use rudf::sparql::algebra::QueryResult;
//!
//! let repository = MemoryRepository::default();
@ -37,7 +36,7 @@
pub mod model;
mod repository;
pub mod rio;
mod rio;
pub mod sparql;
pub(crate) mod store;
@ -48,3 +47,4 @@ pub use crate::store::MemoryRepository;
pub use crate::store::RocksDbRepository;
pub use repository::Repository;
pub use repository::RepositoryConnection;
pub use rio::GraphSyntax;

@ -12,7 +12,6 @@ use std::iter::FromIterator;
/// ```
/// use rudf::model::*;
/// use rudf::model::SimpleGraph;
/// use std::str::FromStr;
///
/// let mut graph = SimpleGraph::default();
/// let ex = NamedNode::new("http://example.com");

@ -9,7 +9,6 @@ use std::fmt;
/// The default string formatter is returning a N-Triples, Turtle and SPARQL compatible representation:
/// ```
/// use rudf::model::NamedNode;
/// use std::str::FromStr;
///
/// assert_eq!(
/// "<http://example.com/foo>",

@ -1,7 +1,7 @@
use crate::model::*;
use crate::sparql::PreparedQuery;
use crate::Result;
use std::io::Read;
use crate::{GraphSyntax, Result};
use std::io::{BufRead, Read};
/// A `Repository` stores a [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset)
/// and allows to query and update it using SPARQL.
@ -15,7 +15,6 @@ use std::io::Read;
/// use rudf::model::*;
/// use rudf::{Repository, RepositoryConnection, MemoryRepository, Result};
/// use crate::rudf::sparql::PreparedQuery;
/// use std::str::FromStr;
/// use rudf::sparql::algebra::QueryResult;
///
/// let repository = MemoryRepository::default();
@ -67,7 +66,6 @@ pub trait RepositoryConnection: Clone {
/// use rudf::{Repository, RepositoryConnection, MemoryRepository};
/// use rudf::sparql::PreparedQuery;
/// use rudf::sparql::algebra::QueryResult;
/// use std::str::FromStr;
///
/// let repository = MemoryRepository::default();
/// let connection = repository.connection().unwrap();
@ -91,7 +89,6 @@ pub trait RepositoryConnection: Clone {
/// ```
/// use rudf::model::*;
/// use rudf::{Repository, RepositoryConnection, MemoryRepository, Result};
/// use std::str::FromStr;
///
/// let repository = MemoryRepository::default();
/// let connection = repository.connection().unwrap();
@ -110,17 +107,44 @@ pub trait RepositoryConnection: Clone {
subject: Option<&NamedOrBlankNode>,
predicate: Option<&NamedNode>,
object: Option<&Term>,
graph_name: Option<&NamedOrBlankNode>,
graph_name: Option<Option<&NamedOrBlankNode>>,
) -> Box<dyn Iterator<Item = Result<Quad>> + 'a>
where
Self: 'a;
/// Checks if this dataset contains a given quad
/// Loads a graph file (i.e. triples) into the repository
///
/// Usage example:
/// ```
/// use rudf::model::*;
/// use rudf::{Repository, RepositoryConnection, MemoryRepository, Result, GraphSyntax};
///
/// let repository = MemoryRepository::default();
/// let connection = repository.connection().unwrap();
///
/// // insertion
/// let file = b"<http://example.com> <http://example.com> <http://example.com> .";
/// connection.load_graph(file.as_ref(), GraphSyntax::NTriples, None, None);
///
/// // quad filter
/// let results: Result<Vec<Quad>> = connection.quads_for_pattern(None, None, None, None).collect();
/// let ex = NamedNode::new("http://example.com");
/// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), None)], results.unwrap());
/// ```
fn load_graph(
&self,
reader: impl BufRead,
syntax: GraphSyntax,
to_graph_name: Option<&NamedOrBlankNode>,
base_iri: Option<&str>,
) -> Result<()>;
/// Checks if this repository contains a given quad
fn contains(&self, quad: &Quad) -> Result<bool>;
/// Adds a quad to this dataset
/// Adds a quad to this repository
fn insert(&self, quad: &Quad) -> Result<()>;
/// Removes a quad from this dataset
/// Removes a quad from this repository
fn remove(&self, quad: &Quad) -> Result<()>;
}

@ -1,93 +1,10 @@
//! Implementations of serializers and deserializers for usual RDF syntaxes
use crate::model::*;
use crate::Result;
use rio_api::model as rio;
use rio_api::parser::TripleParser;
use rio_turtle::{NTriplesParser, TurtleParser};
use rio_xml::RdfXmlParser;
use std::collections::BTreeMap;
use std::io::BufRead;
/// Reads a [N-Triples](https://www.w3.org/TR/n-triples/) file from a Rust `BufRead` and returns an iterator of the read `Triple`s
pub fn read_ntriples<R: BufRead>(reader: R) -> Result<impl Iterator<Item = Result<Triple>>> {
let mut bnode_map = BTreeMap::default();
Ok(NTriplesParser::new(reader)?.into_iter(move |t| convert_triple(t, &mut bnode_map)))
}
/// Reads a [Turtle](https://www.w3.org/TR/turtle/) file from a Rust `BufRead` and returns an iterator of the read `Triple`s
pub fn read_turtle<'a, R: BufRead + 'a>(
reader: R,
base_url: Option<&'a str>,
) -> Result<impl Iterator<Item = Result<Triple>> + 'a> {
let mut bnode_map = BTreeMap::default();
Ok(TurtleParser::new(reader, base_url.unwrap_or(""))?
.into_iter(move |t| convert_triple(t, &mut bnode_map)))
}
/// Reads a [RDF XML](https://www.w3.org/TR/rdf-syntax-grammar/) file from a Rust `BufRead` and returns an iterator of the read `Triple`s
pub fn read_rdf_xml<'a, R: BufRead + 'a>(
reader: R,
base_url: Option<&'a str>,
) -> Result<impl Iterator<Item = Result<Triple>> + 'a> {
let mut bnode_map = BTreeMap::default();
Ok(RdfXmlParser::new(reader, base_url.unwrap_or(""))?
.into_iter(move |t| convert_triple(t, &mut bnode_map)))
}
fn convert_triple(
value: rio::Triple,
bnodes_map: &mut BTreeMap<String, BlankNode>,
) -> Result<Triple> {
let t = Triple::new(
convert_named_or_blank_node(value.subject, bnodes_map)?,
convert_named_node(value.predicate)?,
convert_term(value.object, bnodes_map)?,
);
// println!("{}", t);
Ok(t)
}
fn convert_term(value: rio::Term, bnodes_map: &mut BTreeMap<String, BlankNode>) -> Result<Term> {
Ok(match value {
rio::Term::NamedNode(v) => convert_named_node(v)?.into(),
rio::Term::BlankNode(v) => convert_blank_node(v, bnodes_map).into(),
rio::Term::Literal(v) => convert_literal(v)?.into(),
})
}
fn convert_named_or_blank_node(
value: rio::NamedOrBlankNode,
bnodes_map: &mut BTreeMap<String, BlankNode>,
) -> Result<NamedOrBlankNode> {
Ok(match value {
rio::NamedOrBlankNode::NamedNode(v) => convert_named_node(v)?.into(),
rio::NamedOrBlankNode::BlankNode(v) => convert_blank_node(v, bnodes_map).into(),
})
}
fn convert_named_node(value: rio::NamedNode) -> Result<NamedNode> {
Ok(NamedNode::new(value.iri))
}
fn convert_blank_node(
value: rio::BlankNode,
bnodes_map: &mut BTreeMap<String, BlankNode>,
) -> BlankNode {
bnodes_map
.entry(value.id.to_string())
.or_insert_with(BlankNode::default)
.clone()
}
fn convert_literal(value: rio::Literal) -> Result<Literal> {
Ok(match value {
rio::Literal::Simple { value } => Literal::new_simple_literal(value),
rio::Literal::LanguageTaggedString { value, language } => {
Literal::new_language_tagged_literal(value, LanguageTag::parse(language)?)
}
rio::Literal::Typed { value, datatype } => {
Literal::new_typed_literal(value, convert_named_node(datatype)?)
}
})
/// [RDF graph](https://www.w3.org/TR/rdf11-concepts/#dfn-graph) serialization formats.
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub enum GraphSyntax {
/// [N-Triples](https://www.w3.org/TR/n-triples/)
NTriples,
/// [Turtle](https://www.w3.org/TR/turtle/)
Turtle,
/// [RDF XML](https://www.w3.org/TR/rdf-syntax-grammar/)
RdfXml,
}

@ -1280,9 +1280,7 @@ impl<'a> fmt::Display for SparqlAggregation<'a> {
}
fn fmt_str(value: &str) -> rio::Literal {
rio::Literal::Simple {
value: value.into(),
}
rio::Literal::Simple { value }
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)]

@ -739,7 +739,7 @@ impl<'a, S: StoreConnection + 'a> SimpleEvaluator<S> {
fn to_string_id(&self, term: EncodedTerm) -> Option<u64> {
match term {
EncodedTerm::DefaultGraph {} => None,
EncodedTerm::DefaultGraph => None,
EncodedTerm::NamedNode { iri_id } => Some(iri_id),
EncodedTerm::BlankNode(_) => None,
EncodedTerm::StringLiteral { value_id }
@ -1201,7 +1201,7 @@ impl<'a, S: StoreConnection> Iterator for UnionIterator<'a, S> {
type Item = Result<EncodedTuple>;
fn next(&mut self) -> Option<Result<EncodedTuple>> {
while let Some(tuple) = self.current.pop() {
if let Some(tuple) = self.current.pop() {
return Some(tuple);
}
match self.input_iter.next()? {
@ -1320,7 +1320,7 @@ impl<'a, S: StoreConnection> Iterator for DescribeIterator<'a, S> {
type Item = Result<Triple>;
fn next(&mut self) -> Option<Result<Triple>> {
while let Some(quad) = self.quads.pop() {
if let Some(quad) = self.quads.pop() {
return Some(match quad {
Ok(quad) => self
.store

@ -18,7 +18,6 @@ use std::sync::RwLockWriteGuard;
/// use rudf::model::*;
/// use rudf::{Repository, RepositoryConnection, MemoryRepository, Result};
/// use crate::rudf::sparql::PreparedQuery;
/// use std::str::FromStr;
/// use rudf::sparql::algebra::QueryResult;
///
/// let repository = MemoryRepository::default();

@ -12,8 +12,12 @@ pub use crate::store::rocksdb::RocksDbRepository;
use crate::model::*;
use crate::sparql::SimplePreparedQuery;
use crate::store::numeric_encoder::*;
use crate::{RepositoryConnection, Result};
use std::io::Read;
use crate::{GraphSyntax, RepositoryConnection, Result};
use rio_api::parser::TripleParser;
use rio_turtle::{NTriplesParser, TurtleParser};
use rio_xml::RdfXmlParser;
use std::collections::HashMap;
use std::io::{BufRead, Read};
use std::iter::{once, Iterator};
/// Defines the `Store` traits that is used to have efficient binary storage
@ -64,7 +68,7 @@ impl<S: StoreConnection> RepositoryConnection for StoreRepositoryConnection<S> {
subject: Option<&NamedOrBlankNode>,
predicate: Option<&NamedNode>,
object: Option<&Term>,
graph_name: Option<&NamedOrBlankNode>,
graph_name: Option<Option<&NamedOrBlankNode>>,
) -> Box<dyn Iterator<Item = Result<Quad>> + 'a>
where
Self: 'a,
@ -95,10 +99,14 @@ impl<S: StoreConnection> RepositoryConnection for StoreRepositoryConnection<S> {
None
};
let graph_name = if let Some(graph_name) = graph_name {
match encoder.encode_named_or_blank_node(graph_name) {
Ok(subject) => Some(subject),
Err(error) => return Box::new(once(Err(error))),
}
Some(if let Some(graph_name) = graph_name {
match encoder.encode_named_or_blank_node(graph_name) {
Ok(graph_name) => graph_name,
Err(error) => return Box::new(once(Err(error))),
}
} else {
EncodedTerm::DefaultGraph
})
} else {
None
};
@ -110,6 +118,27 @@ impl<S: StoreConnection> RepositoryConnection for StoreRepositoryConnection<S> {
)
}
fn load_graph(
&self,
reader: impl BufRead,
syntax: GraphSyntax,
to_graph_name: Option<&NamedOrBlankNode>,
base_iri: Option<&str>,
) -> Result<()> {
let base_iri = base_iri.unwrap_or(&"");
match syntax {
GraphSyntax::NTriples => {
self.load_from_triple_parser(NTriplesParser::new(reader)?, to_graph_name)
}
GraphSyntax::Turtle => {
self.load_from_triple_parser(TurtleParser::new(reader, base_iri)?, to_graph_name)
}
GraphSyntax::RdfXml => {
self.load_from_triple_parser(RdfXmlParser::new(reader, base_iri)?, to_graph_name)
}
}
}
fn contains(&self, quad: &Quad) -> Result<bool> {
self.inner
.contains(&self.inner.encoder().encode_quad(quad)?)
@ -123,3 +152,33 @@ impl<S: StoreConnection> RepositoryConnection for StoreRepositoryConnection<S> {
self.inner.remove(&self.inner.encoder().encode_quad(quad)?)
}
}
impl<S: StoreConnection> StoreRepositoryConnection<S> {
fn load_from_triple_parser<P: TripleParser>(
&self,
mut parser: P,
to_graph_name: Option<&NamedOrBlankNode>,
) -> Result<()>
where
P::Error: Send + Sync + 'static,
{
//TODO: handle errors
let mut bnode_map = HashMap::default();
let encoder = self.inner.encoder();
let graph_name = if let Some(graph_name) = to_graph_name {
encoder.encode_named_or_blank_node(graph_name)?
} else {
EncodedTerm::DefaultGraph
};
parser.parse_all(&mut move |t| {
self.inner
.insert(
&encoder
.encode_rio_triple_in_graph(t, graph_name, &mut bnode_map)
.unwrap(),
)
.unwrap()
})?;
Ok(())
}
}

@ -8,8 +8,9 @@ use failure::format_err;
use failure::Backtrace;
use failure::Fail;
use ordered_float::OrderedFloat;
use rio_api::model as rio;
use rust_decimal::Decimal;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use std::io::Read;
use std::io::Write;
use std::ops::Deref;
@ -142,7 +143,7 @@ const TYPE_NAIVE_DATE_TIME_LITERAL: u8 = 14;
const TYPE_NAIVE_DATE_LITERAL: u8 = 15;
const TYPE_NAIVE_TIME_LITERAL: u8 = 16;
pub static ENCODED_DEFAULT_GRAPH: EncodedTerm = EncodedTerm::DefaultGraph {};
pub static ENCODED_DEFAULT_GRAPH: EncodedTerm = EncodedTerm::DefaultGraph;
pub static ENCODED_EMPTY_STRING_LITERAL: EncodedTerm = EncodedTerm::StringLiteral {
value_id: EMPTY_STRING_ID,
};
@ -179,7 +180,7 @@ pub static ENCODED_XSD_DATE_TIME_NAMED_NODE: EncodedTerm = EncodedTerm::NamedNod
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash)]
pub enum EncodedTerm {
DefaultGraph {},
DefaultGraph,
NamedNode { iri_id: u64 },
BlankNode(Uuid),
StringLiteral { value_id: u64 },
@ -378,7 +379,7 @@ pub trait TermReader {
impl<R: Read> TermReader for R {
fn read_term(&mut self) -> Result<EncodedTerm> {
match self.read_u8()? {
TYPE_DEFAULT_GRAPH_ID => Ok(EncodedTerm::DefaultGraph {}),
TYPE_DEFAULT_GRAPH_ID => Ok(EncodedTerm::DefaultGraph),
TYPE_NAMED_NODE_ID => Ok(EncodedTerm::NamedNode {
iri_id: self.read_u64::<LittleEndian>()?,
}),
@ -496,7 +497,7 @@ impl<R: Write> TermWriter for R {
fn write_term(&mut self, term: EncodedTerm) -> Result<()> {
self.write_u8(term.type_id())?;
match term {
EncodedTerm::DefaultGraph {} => {}
EncodedTerm::DefaultGraph => {}
EncodedTerm::NamedNode { iri_id } => self.write_u64::<LittleEndian>(iri_id)?,
EncodedTerm::BlankNode(id) => self.write_all(id.as_bytes())?,
EncodedTerm::StringLiteral { value_id } => {
@ -576,8 +577,8 @@ impl<S: StringStore> Encoder<S> {
}
pub fn encode_named_node(&self, named_node: &NamedNode) -> Result<EncodedTerm> {
Ok(EncodedTerm::NamedNode {
iri_id: self.string_store.insert_str(named_node.as_str())?,
self.encode_rio_named_node(rio::NamedNode {
iri: named_node.as_str(),
})
}
@ -688,9 +689,108 @@ impl<S: StringStore> Encoder<S> {
})
}
pub fn encode_rio_named_node(&self, named_node: rio::NamedNode) -> Result<EncodedTerm> {
Ok(EncodedTerm::NamedNode {
iri_id: self.string_store.insert_str(named_node.iri)?,
})
}
pub fn encode_rio_blank_node(
&self,
blank_node: rio::BlankNode,
bnodes_map: &mut HashMap<String, Uuid>,
) -> Result<EncodedTerm> {
Ok(if let Some(uuid) = bnodes_map.get(blank_node.id) {
EncodedTerm::BlankNode(*uuid)
} else {
let uuid = Uuid::new_v4();
bnodes_map.insert(blank_node.id.to_owned(), uuid);
EncodedTerm::BlankNode(uuid)
})
}
pub fn encode_rio_literal(&self, literal: rio::Literal) -> Result<EncodedTerm> {
match literal {
rio::Literal::Simple { value } => Ok(EncodedTerm::StringLiteral {
value_id: self.string_store.insert_str(value)?,
}),
rio::Literal::LanguageTaggedString { value, language } => {
Ok(EncodedTerm::LangStringLiteral {
value_id: self.string_store.insert_str(value)?,
language_id: self
.string_store
.insert_str(LanguageTag::parse(language)?.as_str())?,
//TODO: avoid
})
}
rio::Literal::Typed { value, datatype } => {
//TODO: optimize
self.encode_literal(&Literal::new_typed_literal(
value,
NamedNode::new(datatype.iri),
))
}
}
}
pub fn encode_rio_named_or_blank_node(
&self,
term: rio::NamedOrBlankNode,
bnodes_map: &mut HashMap<String, Uuid>,
) -> Result<EncodedTerm> {
match term {
rio::NamedOrBlankNode::NamedNode(named_node) => self.encode_rio_named_node(named_node),
rio::NamedOrBlankNode::BlankNode(blank_node) => {
self.encode_rio_blank_node(blank_node, bnodes_map)
}
}
}
pub fn encode_rio_term(
&self,
term: rio::Term,
bnodes_map: &mut HashMap<String, Uuid>,
) -> Result<EncodedTerm> {
match term {
rio::Term::NamedNode(named_node) => self.encode_rio_named_node(named_node),
rio::Term::BlankNode(blank_node) => self.encode_rio_blank_node(blank_node, bnodes_map),
rio::Term::Literal(literal) => self.encode_rio_literal(literal),
}
}
pub fn encode_rio_quad(
&self,
quad: rio::Quad,
bnodes_map: &mut HashMap<String, Uuid>,
) -> Result<EncodedQuad> {
Ok(EncodedQuad {
subject: self.encode_rio_named_or_blank_node(quad.subject, bnodes_map)?,
predicate: self.encode_rio_named_node(quad.predicate)?,
object: self.encode_rio_term(quad.object, bnodes_map)?,
graph_name: match quad.graph_name {
Some(graph_name) => self.encode_rio_named_or_blank_node(graph_name, bnodes_map)?,
None => ENCODED_DEFAULT_GRAPH,
},
})
}
pub fn encode_rio_triple_in_graph(
&self,
triple: rio::Triple,
graph_name: EncodedTerm,
bnodes_map: &mut HashMap<String, Uuid>,
) -> Result<EncodedQuad> {
Ok(EncodedQuad {
subject: self.encode_rio_named_or_blank_node(triple.subject, bnodes_map)?,
predicate: self.encode_rio_named_node(triple.predicate)?,
object: self.encode_rio_term(triple.object, bnodes_map)?,
graph_name,
})
}
pub fn decode_term(&self, encoded: EncodedTerm) -> Result<Term> {
match encoded {
EncodedTerm::DefaultGraph {} => {
EncodedTerm::DefaultGraph => {
Err(format_err!("The default graph tag is not a valid term"))
}
EncodedTerm::NamedNode { iri_id } => {
@ -764,7 +864,7 @@ impl<S: StringStore> Encoder<S> {
self.decode_named_node(encoded.predicate)?,
self.decode_term(encoded.object)?,
match encoded.graph_name {
EncodedTerm::DefaultGraph {} => None,
EncodedTerm::DefaultGraph => None,
graph_name => Some(self.decode_named_or_blank_node(graph_name)?),
},
))

@ -28,7 +28,6 @@ use std::sync::Mutex;
/// use rudf::model::*;
/// use rudf::{Repository, RepositoryConnection, RocksDbRepository, Result};
/// use crate::rudf::sparql::PreparedQuery;
/// use std::str::FromStr;
/// use rudf::sparql::algebra::QueryResult;
///
/// let repository = RocksDbRepository::open("example.db").unwrap();

@ -1,368 +0,0 @@
///! Integration tests based on [RDF 1.1 Test Cases](https://www.w3.org/TR/rdf11-testcases/)
use failure::format_err;
use rudf::model::vocab::rdf;
use rudf::model::vocab::rdfs;
use rudf::model::*;
use rudf::rio::read_ntriples;
use rudf::rio::read_rdf_xml;
use rudf::rio::read_turtle;
use rudf::Result;
use std::fmt;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
#[test]
fn turtle_w3c_testsuite() {
let manifest_url = "http://w3c.github.io/rdf-tests/turtle/manifest.ttl";
for test_result in TestManifest::new(manifest_url) {
let test = test_result.unwrap();
if test.kind == "TestTurtlePositiveSyntax" {
if let Err(error) = load_turtle(test.action.as_str()) {
assert!(false, "Failure on {} with error: {}", test, error)
}
} else if test.kind == "TestTurtleNegativeSyntax" {
assert!(
load_turtle(test.action.as_str()).is_err(),
"Failure on {}",
test
);
} else if test.kind == "TestTurtleEval" {
match load_turtle(test.action.as_str()) {
Ok(action_graph) => match load_turtle(test.result.as_ref().unwrap()) {
Ok(result_graph) => assert!(
action_graph.is_isomorphic(&result_graph),
"Failure on {}. Expected file:\n{}\nParsed file:\n{}\n",
test,
result_graph,
action_graph
),
Err(error) => assert!(
false,
"Failure to parse the Turtle result file {} of {} with error: {}",
test.result.as_ref().unwrap(),
test,
error
),
},
Err(error) => assert!(false, "Failure to parse {} with error: {}", test, error),
}
} else if test.kind == "TestTurtleNegativeEval" {
let action_graph = load_turtle(test.action.as_str());
let result_graph = test
.result
.clone()
.map(|r| load_turtle(r.as_str()))
.unwrap_or_else(|| Ok(SimpleGraph::default()));
assert!(
action_graph.is_err()
|| !action_graph.unwrap().is_isomorphic(&result_graph.unwrap()),
"Failure on {}",
test
);
} else {
assert!(false, "Not supported test: {}", test);
}
}
}
#[test]
fn ntriples_w3c_testsuite() {
let manifest_url = "http://w3c.github.io/rdf-tests/ntriples/manifest.ttl";
for test_result in TestManifest::new(manifest_url) {
let test = test_result.unwrap();
if test.kind == "TestNTriplesPositiveSyntax" {
if let Err(error) = load_ntriples(test.action.as_str()) {
assert!(false, "Failure on {} with error: {}", test, error)
}
} else if test.kind == "TestNTriplesNegativeSyntax" {
if let Ok(graph) = load_ntriples(test.action.as_str()) {
assert!(false, "Failure on {}, found:\n{}", test, graph);
}
} else {
assert!(false, "Not supported test: {}", test);
}
}
}
#[test]
fn rdf_xml_w3c_testsuite() -> Result<()> {
let manifest_url = "http://www.w3.org/2013/RDFXMLTests/manifest.ttl";
for test_result in TestManifest::new(manifest_url) {
let test = test_result?;
if test.kind == "TestXMLNegativeSyntax" {
assert!(
load_rdf_xml(test.action.as_str()).is_err(),
"Failure on {}",
test
);
} else if test.kind == "TestXMLEval" {
match load_rdf_xml(test.action.as_str()) {
Ok(action_graph) => match load_ntriples(test.result.as_ref().unwrap()) {
Ok(result_graph) => assert!(
action_graph.is_isomorphic(&result_graph),
"Failure on {}. Expected file:\n{}\nParsed file:\n{}\n",
test,
result_graph,
action_graph
),
Err(error) => assert!(
false,
"Failure to parse the RDF XML result file {} of {} with error: {}",
test.result.clone().unwrap(),
test,
error
),
},
Err(error) => assert!(false, "Failure to parse {} with error: {}", test, error),
}
} else {
assert!(false, "Not supported test: {}", test);
}
}
Ok(())
}
fn load_turtle(url: &str) -> Result<SimpleGraph> {
read_turtle(read_file(url)?, Some(url))?.collect()
}
fn load_ntriples(url: &str) -> Result<SimpleGraph> {
read_ntriples(read_file(url)?)?.collect()
}
fn load_rdf_xml(url: &str) -> Result<SimpleGraph> {
read_rdf_xml(read_file(url)?, Some(url))?.collect()
}
fn to_relative_path(url: &str) -> Result<String> {
if url.starts_with("http://w3c.github.io/rdf-tests/") {
Ok(url.replace("http://w3c.github.io/", ""))
} else if url.starts_with("http://www.w3.org/2013/RDFXMLTests/") {
Ok(url.replace("http://www.w3.org/2013/RDFXMLTests/", "rdf-tests/rdf-xml/"))
} else {
Err(format_err!("Not supported url for file: {}", url))
}
}
fn read_file(url: &str) -> Result<impl BufRead> {
let mut base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
base_path.push("tests");
base_path.push(to_relative_path(url)?);
Ok(BufReader::new(File::open(&base_path).map_err(|e| {
format_err!("Opening file {} failed with {}", base_path.display(), e)
})?))
}
pub struct Test {
pub id: NamedNode,
pub kind: String,
pub name: Option<String>,
pub comment: Option<String>,
pub action: String,
pub result: Option<String>,
}
impl fmt::Display for Test {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
for name in &self.name {
write!(f, " named \"{}\"", name)?;
}
for comment in &self.comment {
write!(f, " with comment \"{}\"", comment)?;
}
write!(f, " on file \"{}\"", self.action)?;
Ok(())
}
}
pub struct TestManifest {
graph: SimpleGraph,
tests_to_do: Vec<Term>,
manifests_to_do: Vec<String>,
}
impl TestManifest {
pub fn new(url: impl Into<String>) -> TestManifest {
Self {
graph: SimpleGraph::default(),
tests_to_do: Vec::default(),
manifests_to_do: vec![url.into()],
}
}
}
pub mod mf {
use lazy_static::lazy_static;
use rudf::model::NamedNode;
lazy_static! {
pub static ref INCLUDE: NamedNode =
NamedNode::new("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#include");
pub static ref ENTRIES: NamedNode =
NamedNode::new("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#entries");
pub static ref NAME: NamedNode =
NamedNode::new("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#name");
pub static ref ACTION: NamedNode =
NamedNode::new("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#action");
pub static ref RESULT: NamedNode =
NamedNode::new("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#result");
}
}
impl Iterator for TestManifest {
type Item = Result<Test>;
fn next(&mut self) -> Option<Result<Test>> {
match self.tests_to_do.pop() {
Some(Term::NamedNode(test_node)) => {
let test_subject = NamedOrBlankNode::from(test_node.clone());
let kind = match self
.graph
.object_for_subject_predicate(&test_subject, &rdf::TYPE)
{
Some(Term::NamedNode(c)) => match c.as_str().split("#").last() {
Some(k) => k.to_string(),
None => return Some(Err(format_err!("no type"))),
},
_ => return Some(Err(format_err!("no type"))),
};
let name = match self
.graph
.object_for_subject_predicate(&test_subject, &mf::NAME)
{
Some(Term::Literal(c)) => Some(c.value().to_string()),
_ => None,
};
let comment = match self
.graph
.object_for_subject_predicate(&test_subject, &rdfs::COMMENT)
{
Some(Term::Literal(c)) => Some(c.value().to_string()),
_ => None,
};
let action = match self
.graph
.object_for_subject_predicate(&test_subject, &*mf::ACTION)
{
Some(Term::NamedNode(n)) => n.as_str().to_string(),
Some(_) => return Some(Err(format_err!("invalid action"))),
None => return Some(Err(format_err!("action not found"))),
};
let result = match self
.graph
.object_for_subject_predicate(&test_subject, &*mf::RESULT)
{
Some(Term::NamedNode(n)) => Some(n.as_str().to_string()),
Some(_) => return Some(Err(format_err!("invalid result"))),
None => None,
};
Some(Ok(Test {
id: test_node,
kind,
name,
comment,
action,
result,
}))
}
Some(_) => Some(Err(format_err!("invalid test list"))),
None => {
match self.manifests_to_do.pop() {
Some(url) => {
let manifest =
NamedOrBlankNode::from(NamedNode::new(url.as_str().to_string()));
match load_turtle(&url) {
Ok(g) => self.graph.extend(g.into_iter()),
Err(e) => return Some(Err(e.into())),
}
// New manifests
match self
.graph
.object_for_subject_predicate(&manifest, &*mf::INCLUDE)
{
Some(Term::BlankNode(list)) => {
self.manifests_to_do.extend(
RdfListIterator::iter(&self.graph, list.clone().into())
.filter_map(|m| match m {
Term::NamedNode(nm) => Some(nm.as_str().to_string()),
_ => None,
}),
);
}
Some(_) => return Some(Err(format_err!("invalid tests list"))),
None => (),
}
// New tests
match self
.graph
.object_for_subject_predicate(&manifest, &*mf::ENTRIES)
{
Some(Term::BlankNode(list)) => {
self.tests_to_do.extend(RdfListIterator::iter(
&self.graph,
list.clone().into(),
));
}
Some(term) => {
return Some(Err(format_err!(
"Invalid tests list. Got term {}",
term
)));
}
None => (),
}
}
None => return None,
}
self.next()
}
}
}
}
pub struct RdfListIterator<'a> {
graph: &'a SimpleGraph,
current_node: Option<NamedOrBlankNode>,
}
impl<'a> RdfListIterator<'a> {
fn iter(graph: &'a SimpleGraph, root: NamedOrBlankNode) -> RdfListIterator<'a> {
RdfListIterator {
graph,
current_node: Some(root),
}
}
}
impl<'a> Iterator for RdfListIterator<'a> {
type Item = Term;
fn next(&mut self) -> Option<Term> {
match self.current_node.clone() {
Some(current) => {
let result = self
.graph
.object_for_subject_predicate(&current, &rdf::FIRST);
self.current_node = match self
.graph
.object_for_subject_predicate(&current, &rdf::REST)
{
Some(Term::NamedNode(ref n)) if *n == *rdf::NIL => None,
Some(Term::NamedNode(n)) => Some(n.clone().into()),
Some(Term::BlankNode(n)) => Some(n.clone().into()),
_ => None,
};
result.cloned()
}
None => None,
}
}
}

@ -3,14 +3,12 @@ use failure::format_err;
use rudf::model::vocab::rdf;
use rudf::model::vocab::rdfs;
use rudf::model::*;
use rudf::rio::read_rdf_xml;
use rudf::rio::read_turtle;
use rudf::sparql::algebra::Query;
use rudf::sparql::algebra::QueryResult;
use rudf::sparql::parser::read_sparql_query;
use rudf::sparql::xml_results::read_xml_results;
use rudf::sparql::PreparedQuery;
use rudf::{MemoryRepository, Repository, RepositoryConnection, Result};
use rudf::{GraphSyntax, MemoryRepository, Repository, RepositoryConnection, Result};
use std::fmt;
use std::fs::File;
use std::io::{BufRead, BufReader};
@ -123,29 +121,17 @@ fn sparql_w3c_query_evaluation_testsuite() {
continue;
}
if test.kind == "QueryEvaluationTest" {
let repository = match &test.data {
Some(data) => {
let repository = MemoryRepository::default();
let connection = repository.connection().unwrap();
load_graph(&data)
.unwrap()
.into_iter()
.for_each(|triple| connection.insert(&triple.in_graph(None)).unwrap());
repository
}
None => MemoryRepository::default(),
};
let repository = MemoryRepository::default();
if let Some(data) = &test.data {
load_graph_to_repository(&data, &repository.connection().unwrap(), None).unwrap();
}
for graph_data in &test.graph_data {
let graph_name = NamedNode::new(graph_data);
let connection = repository.connection().unwrap();
load_graph(&graph_data)
.unwrap()
.into_iter()
.for_each(move |triple| {
connection
.insert(&triple.in_graph(Some(graph_name.clone().into())))
.unwrap()
});
load_graph_to_repository(
&graph_data,
&repository.connection().unwrap(),
Some(&NamedNode::new(graph_data).into()),
)
.unwrap();
}
match repository
.connection()
@ -199,13 +185,29 @@ fn repository_to_string(repository: impl Repository) -> String {
}
fn load_graph(url: &str) -> Result<SimpleGraph> {
if url.ends_with(".ttl") {
read_turtle(read_file(url)?, Some(url))?.collect()
let repository = MemoryRepository::default();
load_graph_to_repository(url, &repository.connection().unwrap(), None)?;
Ok(repository
.connection()
.unwrap()
.quads_for_pattern(None, None, None, Some(None))
.map(|q| q.unwrap().into_triple())
.collect())
}
fn load_graph_to_repository(
url: &str,
connection: &<&MemoryRepository as Repository>::Connection,
to_graph_name: Option<&NamedOrBlankNode>,
) -> Result<()> {
let syntax = if url.ends_with(".ttl") {
GraphSyntax::Turtle
} else if url.ends_with(".rdf") {
read_rdf_xml(read_file(url)?, Some(url))?.collect()
GraphSyntax::RdfXml
} else {
Err(format_err!("Serialization type not found for {}", url))
}
return Err(format_err!("Serialization type not found for {}", url));
};
connection.load_graph(read_file(url)?, syntax, to_graph_name, Some(url))
}
fn load_sparql_query(url: &str) -> Result<Query> {

@ -27,12 +27,11 @@ use hyper::StatusCode;
use lazy_static::lazy_static;
use mime;
use mime::Mime;
use rudf::rio::read_ntriples;
use rudf::sparql::algebra::QueryResult;
use rudf::sparql::xml_results::write_xml_results;
use rudf::sparql::PreparedQuery;
use rudf::Repository;
use rudf::RepositoryConnection;
use rudf::{GraphSyntax, Repository};
use rudf::{MemoryRepository, RocksDbRepository};
use serde_derive::Deserialize;
use std::fmt::Write;
@ -104,9 +103,12 @@ where
let connection = dataset.connection()?;
if let Some(nt_file) = matches.value_of("ntriples") {
println!("Loading NTriples file {}", nt_file);
for triple in read_ntriples(BufReader::new(File::open(nt_file)?))? {
connection.insert(&triple?.in_graph(None))?
}
connection.load_graph(
BufReader::new(File::open(nt_file)?),
GraphSyntax::NTriples,
None,
None,
)?;
}
}
@ -259,7 +261,7 @@ where
let mut result = String::default();
for triple in triples {
match triple {
Ok(triple) => write!(&mut result, "{}\n", triple).unwrap(),
Ok(triple) => writeln!(&mut result, "{}", triple).unwrap(),
Err(error) => {
return error_to_response(
&state,

Loading…
Cancel
Save