WIP: Available default graphs

pull/46/head
Tpt 4 years ago
parent bc333c5d8f
commit b101ae1c54
  1. 8
      CHANGELOG.md
  2. 157
      lib/src/sparql/dataset.rs
  3. 52
      lib/src/sparql/mod.rs
  4. 31
      python/src/memory_store.rs
  5. 31
      python/src/sled_store.rs
  6. 48
      python/src/sparql.rs
  7. 27
      python/tests/test_store.py

@ -1,3 +1,11 @@
## Master
### Changed
- `QueryOptions::with_default_graph` now takes an `impl Into<GraphName>` instead of an `impl Into<NamedNode>`.
- `QueryOptions::with_named_graph` now takes an `impl Into<NamedOrBlankNode>` instead of an `impl Into<NamedNode>`.
- `pyoxigraph` `query` methods now takes two new parameters, `default_graph` and `named_graphs`. `default_graph_uris` and `named_graph_uris` parameters are deprecated.
## [0.1.0] - 2020-08-09
### Added

@ -1,3 +1,4 @@
use crate::model::{GraphName, NamedOrBlankNode};
use crate::sparql::algebra::DatasetSpec;
use crate::sparql::EvaluationError;
use crate::store::numeric_encoder::{
@ -19,9 +20,28 @@ impl<S: ReadableEncodedStore> DatasetView<S> {
pub fn new(
store: S,
default_graph_as_union: bool,
default_graphs: &[GraphName],
named_graphs: &[NamedOrBlankNode],
dataset: &DatasetSpec,
) -> Result<Self, EvaluationError> {
let dataset = if dataset.is_empty() {
let dataset = if !default_graphs.is_empty() || !named_graphs.is_empty() {
Some(EncodedDatasetSpec {
default: default_graphs
.iter()
.flat_map(|g| store.get_encoded_graph_name(g.as_ref()).transpose())
.collect::<Result<Vec<_>, _>>()
.map_err(|e| e.into())?,
named: named_graphs
.iter()
.flat_map(|g| {
store
.get_encoded_named_or_blank_node(g.as_ref())
.transpose()
})
.collect::<Result<Vec<_>, _>>()
.map_err(|e| e.into())?,
})
} else if dataset.is_empty() {
None
} else {
Some(EncodedDatasetSpec {
@ -46,55 +66,19 @@ impl<S: ReadableEncodedStore> DatasetView<S> {
dataset,
})
}
}
impl<S: ReadableEncodedStore> StrEncodingAware for DatasetView<S> {
type Error = EvaluationError;
type StrId = DatasetStrId<S::StrId>;
}
impl<S: ReadableEncodedStore> StrLookup for DatasetView<S> {
fn get_str(&self, id: DatasetStrId<S::StrId>) -> Result<Option<String>, EvaluationError> {
match id {
DatasetStrId::Store(id) => self.store.get_str(id).map_err(|e| e.into()),
DatasetStrId::Temporary(id) => {
Ok(self.extra.borrow().try_resolve(&id).map(|e| e.to_owned()))
}
}
}
fn get_str_id(&self, value: &str) -> Result<Option<DatasetStrId<S::StrId>>, EvaluationError> {
if let Some(id) = self.extra.borrow().get(value) {
Ok(Some(DatasetStrId::Temporary(id)))
} else {
Ok(self
.store
.get_str_id(value)
.map_err(|e| e.into())?
.map(DatasetStrId::Store))
}
}
}
impl<S: ReadableEncodedStore> ReadableEncodedStore for DatasetView<S> {
type QuadsIter =
Box<dyn Iterator<Item = Result<EncodedQuad<DatasetStrId<S::StrId>>, EvaluationError>>>;
fn encoded_quads_for_pattern(
fn encoded_quads_for_pattern_in_dataset(
&self,
subject: Option<EncodedTerm<Self::StrId>>,
predicate: Option<EncodedTerm<Self::StrId>>,
object: Option<EncodedTerm<Self::StrId>>,
graph_name: Option<EncodedTerm<Self::StrId>>,
subject: Option<EncodedTerm<S::StrId>>,
predicate: Option<EncodedTerm<S::StrId>>,
object: Option<EncodedTerm<S::StrId>>,
graph_name: Option<EncodedTerm<S::StrId>>,
) -> Box<dyn Iterator<Item = Result<EncodedQuad<DatasetStrId<S::StrId>>, EvaluationError>>>
{
if let Some((subject, predicate, object, graph_name)) =
try_map_quad_pattern(subject, predicate, object, graph_name)
{
if let Some(dataset) = &self.dataset {
if let Some(graph_name) = graph_name {
if graph_name == EncodedTerm::DefaultGraph {
let mut iters = dataset
let iters = dataset
.default
.iter()
.map(|graph_name| {
@ -106,16 +90,6 @@ impl<S: ReadableEncodedStore> ReadableEncodedStore for DatasetView<S> {
)
})
.collect::<Vec<_>>();
if self.default_graph_as_union {
iters.extend(dataset.named.iter().map(|graph_name| {
self.store.encoded_quads_for_pattern(
subject,
predicate,
object,
Some(*graph_name),
)
}));
}
Box::new(map_iter(iters.into_iter().flatten()).map(|quad| {
let quad = quad?;
Ok(EncodedQuad::new(
@ -161,27 +135,72 @@ impl<S: ReadableEncodedStore> ReadableEncodedStore for DatasetView<S> {
Ok(quad) => quad.graph_name != EncodedTerm::DefaultGraph,
}),
)
} else if graph_name == Some(EncodedTerm::DefaultGraph) && self.default_graph_as_union {
Box::new(
map_iter(
self.store
.encoded_quads_for_pattern(subject, predicate, object, None),
)
.map(|quad| {
let quad = quad?;
Ok(EncodedQuad::new(
quad.subject,
quad.predicate,
quad.object,
EncodedTerm::DefaultGraph,
))
}),
)
} else {
Box::new(map_iter(self.store.encoded_quads_for_pattern(
subject, predicate, object, graph_name,
)))
}
}
}
impl<S: ReadableEncodedStore> StrEncodingAware for DatasetView<S> {
type Error = EvaluationError;
type StrId = DatasetStrId<S::StrId>;
}
impl<S: ReadableEncodedStore> StrLookup for DatasetView<S> {
fn get_str(&self, id: DatasetStrId<S::StrId>) -> Result<Option<String>, EvaluationError> {
match id {
DatasetStrId::Store(id) => self.store.get_str(id).map_err(|e| e.into()),
DatasetStrId::Temporary(id) => {
Ok(self.extra.borrow().try_resolve(&id).map(|e| e.to_owned()))
}
}
}
fn get_str_id(&self, value: &str) -> Result<Option<DatasetStrId<S::StrId>>, EvaluationError> {
if let Some(id) = self.extra.borrow().get(value) {
Ok(Some(DatasetStrId::Temporary(id)))
} else {
Ok(self
.store
.get_str_id(value)
.map_err(|e| e.into())?
.map(DatasetStrId::Store))
}
}
}
impl<S: ReadableEncodedStore> ReadableEncodedStore for DatasetView<S> {
type QuadsIter =
Box<dyn Iterator<Item = Result<EncodedQuad<DatasetStrId<S::StrId>>, EvaluationError>>>;
fn encoded_quads_for_pattern(
&self,
subject: Option<EncodedTerm<Self::StrId>>,
predicate: Option<EncodedTerm<Self::StrId>>,
object: Option<EncodedTerm<Self::StrId>>,
graph_name: Option<EncodedTerm<Self::StrId>>,
) -> Box<dyn Iterator<Item = Result<EncodedQuad<DatasetStrId<S::StrId>>, EvaluationError>>>
{
if let Some((subject, predicate, object, graph_name)) =
try_map_quad_pattern(subject, predicate, object, graph_name)
{
if graph_name == Some(EncodedTerm::DefaultGraph) && self.default_graph_as_union {
Box::new(
self.encoded_quads_for_pattern_in_dataset(
subject,
predicate,
object,
Some(EncodedTerm::DefaultGraph),
)
.chain(
self.encoded_quads_for_pattern_in_dataset(subject, predicate, object, None),
),
)
} else {
self.encoded_quads_for_pattern_in_dataset(subject, predicate, object, graph_name)
}
} else {
Box::new(empty())
}

@ -13,8 +13,8 @@ mod plan;
mod plan_builder;
mod xml_results;
use crate::model::NamedNode;
use crate::sparql::algebra::{DatasetSpec, QueryVariants};
use crate::model::{GraphName, NamedNode, NamedOrBlankNode};
use crate::sparql::algebra::QueryVariants;
use crate::sparql::dataset::DatasetView;
pub use crate::sparql::error::EvaluationError;
use crate::sparql::eval::SimpleEvaluator;
@ -76,11 +76,9 @@ impl<S: ReadableEncodedStore + 'static> SimplePreparedQuery<S> {
let dataset = Rc::new(DatasetView::new(
store,
options.default_graph_as_union,
if options.dataset.is_empty() {
&dataset
} else {
&options.dataset
},
&options.default_graphs,
&options.named_graphs,
&dataset,
)?);
let (plan, variables) = PlanBuilder::build(dataset.as_ref(), &algebra)?;
SimplePreparedQueryAction::Select {
@ -97,11 +95,9 @@ impl<S: ReadableEncodedStore + 'static> SimplePreparedQuery<S> {
let dataset = Rc::new(DatasetView::new(
store,
options.default_graph_as_union,
if options.dataset.is_empty() {
&dataset
} else {
&options.dataset
},
&options.default_graphs,
&options.named_graphs,
&dataset,
)?);
let (plan, _) = PlanBuilder::build(dataset.as_ref(), &algebra)?;
SimplePreparedQueryAction::Ask {
@ -118,11 +114,9 @@ impl<S: ReadableEncodedStore + 'static> SimplePreparedQuery<S> {
let dataset = Rc::new(DatasetView::new(
store,
options.default_graph_as_union,
if options.dataset.is_empty() {
&dataset
} else {
&options.dataset
},
&options.default_graphs,
&options.named_graphs,
&dataset,
)?);
let (plan, variables) = PlanBuilder::build(dataset.as_ref(), &algebra)?;
SimplePreparedQueryAction::Construct {
@ -143,11 +137,9 @@ impl<S: ReadableEncodedStore + 'static> SimplePreparedQuery<S> {
let dataset = Rc::new(DatasetView::new(
store,
options.default_graph_as_union,
if options.dataset.is_empty() {
&dataset
} else {
&options.dataset
},
&options.default_graphs,
&options.named_graphs,
&dataset,
)?);
let (plan, _) = PlanBuilder::build(dataset.as_ref(), &algebra)?;
SimplePreparedQueryAction::Describe {
@ -183,7 +175,8 @@ impl<S: ReadableEncodedStore + 'static> SimplePreparedQuery<S> {
#[derive(Clone)]
pub struct QueryOptions {
pub(crate) default_graph_as_union: bool,
pub(crate) dataset: DatasetSpec,
pub(crate) default_graphs: Vec<GraphName>,
pub(crate) named_graphs: Vec<NamedOrBlankNode>,
pub(crate) service_handler: Rc<dyn ServiceHandler<Error = EvaluationError>>,
}
@ -192,7 +185,8 @@ impl Default for QueryOptions {
fn default() -> Self {
Self {
default_graph_as_union: false,
dataset: DatasetSpec::default(),
default_graphs: Vec::new(),
named_graphs: Vec::new(),
service_handler: Rc::new(EmptyServiceHandler),
}
}
@ -206,19 +200,19 @@ impl QueryOptions {
self
}
/// Adds a named graph to the set of graphs considered by the SPARQL query as the queried dataset default graph.
/// Adds a graph to the set of graphs considered by the SPARQL query as the queried dataset default graph.
/// It overrides the `FROM` and `FROM NAMED` elements of the evaluated query.
#[inline]
pub fn with_default_graph(mut self, default_graph_name: impl Into<NamedNode>) -> Self {
self.dataset.default.push(default_graph_name.into());
pub fn with_default_graph(mut self, default_graph_name: impl Into<GraphName>) -> Self {
self.default_graphs.push(default_graph_name.into());
self
}
/// Adds a named graph to the set of graphs considered by the SPARQL query as the queried dataset named graphs.
/// It overrides the `FROM` and `FROM NAMED` elements of the evaluated query.
#[inline]
pub fn with_named_graph(mut self, named_graph_name: impl Into<NamedNode>) -> Self {
self.dataset.named.push(named_graph_name.into());
pub fn with_named_graph(mut self, named_graph_name: impl Into<NamedOrBlankNode>) -> Self {
self.named_graphs.push(named_graph_name.into());
self
}

@ -108,11 +108,15 @@ impl PyMemoryStore {
///
/// :param query: the query to execute
/// :type query: str
/// :param use_default_graph_as_union: optional, if the SPARQL query should look for triples in all the dataset graphs by default (i.e. without `GRAPH` operations). Disabled by default.
/// :param use_default_graph_as_union: if the SPARQL query should look for triples in all the dataset graphs by default (i.e. without `GRAPH` operations). Disabled by default.
/// :type use_default_graph_as_union: bool, optional
/// :param default_graph_uris: optional, list of the named graph URIs that should be used as the query default graph. By default the store default graph is used.
/// :param default_graph: list of the graphs that should be used as the query default graph. By default, the store default graph is used.
/// :type default_graph: NamedNode or BlankNode or DefaultGraph or list(NamedNode or BlankNode or DefaultGraph) or None, optional
/// :param named_graphs: list of the named graphs that could be used in SPARQL `GRAPH` clause. By default, all the store named graphs are available.
/// :type named_graphs: list(NamedNode or BlankNode) or None, optional
/// :param default_graph_uris: deprecated, use ``default_graph`` instead
/// :type default_graph_uris: list(NamedNode) or None, optional
/// :param named_graph_uris: optional, list of the named graph URIs that could be used in SPARQL `GRAPH` clause. By default all the store default graphs are available.
/// :param named_graph_uris: deprecated, use ``named_graphs`` instead
/// :type named_graph_uris: list(NamedNode) or None, optional
/// :return: a :py:class:`bool` for ``ASK`` queries, an iterator of :py:class:`Triple` for ``CONSTRUCT`` and ``DESCRIBE`` queries and an iterator of :py:class:`QuerySolution` for ``SELECT`` queries.
/// :rtype: QuerySolutions or QueryTriples or bool
@ -138,32 +142,37 @@ impl PyMemoryStore {
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> store.query('ASK { ?s ?p ?o }')
/// True
#[text_signature = "($self, query, *, use_default_graph_as_union, default_graph_uris, named_graph_uris)"]
#[text_signature = "($self, query, *, use_default_graph_as_union, default_graph_uris, named_graph_uris, default_graph, named_graphs)"]
#[args(
query,
"*",
use_default_graph_as_union = "false",
default_graph_uris = "None",
named_graph_uris = "None"
named_graph_uris = "None",
default_graph = "None",
named_graphs = "None"
)]
fn query(
&self,
query: &str,
use_default_graph_as_union: bool,
default_graph_uris: Option<Vec<PyNamedNode>>,
named_graph_uris: Option<Vec<PyNamedNode>>,
default_graph_uris: Option<&PyAny>,
named_graph_uris: Option<&PyAny>,
default_graph: Option<&PyAny>,
named_graphs: Option<&PyAny>,
py: Python<'_>,
) -> PyResult<PyObject> {
let results = py.allow_threads(move || {
let options = build_query_options(
use_default_graph_as_union,
default_graph_uris,
named_graph_uris,
default_graph,
named_graphs,
)?;
self.inner
let results = self
.inner
.query(query, options)
.map_err(map_evaluation_error)
})?;
.map_err(map_evaluation_error)?;
query_results_to_python(py, results)
}

@ -123,11 +123,15 @@ impl PySledStore {
///
/// :param query: the query to execute
/// :type query: str
/// :param use_default_graph_as_union: optional, if the SPARQL query should look for triples in all the dataset graphs by default (i.e. without `GRAPH` operations). Disabled by default.
/// :param use_default_graph_as_union: if the SPARQL query should look for triples in all the dataset graphs by default (i.e. without `GRAPH` operations). Disabled by default.
/// :type use_default_graph_as_union: bool, optional
/// :param default_graph_uris: optional, list of the named graph URIs that should be used as the query default graph. By default the store default graph is used.
/// :param default_graph: list of the graphs that should be used as the query default graph. By default, the store default graph is used.
/// :type default_graph: NamedNode or BlankNode or DefaultGraph or list(NamedNode or BlankNode or DefaultGraph) or None, optional
/// :param named_graphs: list of the named graphs that could be used in SPARQL `GRAPH` clause. By default, all the store named graphs are available.
/// :type named_graphs: list(NamedNode or BlankNode) or None, optional
/// :param default_graph_uris: deprecated, use ``default_graph`` instead
/// :type default_graph_uris: list(NamedNode) or None, optional
/// :param named_graph_uris: optional, list of the named graph URIs that could be used in SPARQL `GRAPH` clause. By default all the store default graphs are available.
/// :param named_graph_uris: deprecated, use ``named_graphs`` instead
/// :type named_graph_uris: list(NamedNode) or None, optional
/// :return: a :py:class:`bool` for ``ASK`` queries, an iterator of :py:class:`Triple` for ``CONSTRUCT`` and ``DESCRIBE`` queries and an iterator of :py:class:`QuerySolution` for ``SELECT`` queries.
/// :rtype: QuerySolutions or QueryTriples or bool
@ -154,32 +158,37 @@ impl PySledStore {
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> store.query('ASK { ?s ?p ?o }')
/// True
#[text_signature = "($self, query, *, use_default_graph_as_union, default_graph_uris, named_graph_uris)"]
#[text_signature = "($self, query, *, use_default_graph_as_union, default_graph_uris, named_graph_uris, default_graph, named_graphs)"]
#[args(
query,
"*",
use_default_graph_as_union = "false",
default_graph_uris = "None",
named_graph_uris = "None"
named_graph_uris = "None",
default_graph = "None",
named_graphs = "None"
)]
fn query(
&self,
query: &str,
use_default_graph_as_union: bool,
default_graph_uris: Option<Vec<PyNamedNode>>,
named_graph_uris: Option<Vec<PyNamedNode>>,
default_graph_uris: Option<&PyAny>,
named_graph_uris: Option<&PyAny>,
default_graph: Option<&PyAny>,
named_graphs: Option<&PyAny>,
py: Python<'_>,
) -> PyResult<PyObject> {
let results = py.allow_threads(move || {
let options = build_query_options(
use_default_graph_as_union,
default_graph_uris,
named_graph_uris,
default_graph,
named_graphs,
)?;
self.inner
let results = self
.inner
.query(query, options)
.map_err(map_evaluation_error)
})?;
.map_err(map_evaluation_error)?;
query_results_to_python(py, results)
}

@ -7,33 +7,57 @@ use pyo3::{PyIterProtocol, PyMappingProtocol, PyNativeType, PyObjectProtocol};
pub fn build_query_options(
use_default_graph_as_union: bool,
default_graph_uris: Option<Vec<PyNamedNode>>,
named_graph_uris: Option<Vec<PyNamedNode>>,
default_graph_uris: Option<&PyAny>,
named_graph_uris: Option<&PyAny>,
default_graph: Option<&PyAny>,
named_graphs: Option<&PyAny>,
) -> PyResult<QueryOptions> {
if default_graph_uris.is_some() && default_graph.is_some() {
return Err(ValueError::py_err(
"The query() method default_graph and default_graph_uris parameters cannot be set at the same time",
));
}
if named_graph_uris.is_some() && named_graphs.is_some() {
return Err(ValueError::py_err(
"The query() method named_graphs and named_graph_uris parameters cannot be set at the same time",
));
}
let mut options = QueryOptions::default();
if use_default_graph_as_union {
options = options.with_default_graph_as_union();
}
if let Some(default_graph_uris) = default_graph_uris {
if default_graph_uris.is_empty() {
if let Some(default_graph) = default_graph.or(default_graph_uris) {
if let Ok(default_graphs) = default_graph.iter() {
if default_graph.is_empty()? {
return Err(ValueError::py_err(
"The list of the default graph URIs could not be empty",
"The query() method default_graph argument cannot be empty list",
));
}
for default_graph_uri in default_graph_uris {
options = options.with_default_graph(default_graph_uri);
for default_graph in default_graphs {
options = options.with_default_graph(extract_graph_name(default_graph?)?);
}
} else if let Ok(default_graph) = extract_graph_name(default_graph) {
options = options.with_default_graph(default_graph);
} else {
return Err(ValueError::py_err(
format!("The query() method default_graph argument should be a NamedNode, a BlankNode, the DefaultGraph or a not empty list of them. {} found", default_graph.get_type()
)));
}
}
if let Some(named_graph_uris) = named_graph_uris {
if named_graph_uris.is_empty() {
if let Some(named_graphs) = named_graphs.or(named_graph_uris) {
if named_graphs.is_empty()? {
return Err(ValueError::py_err(
"The list of the named graph URIs could not be empty",
"The query() method nammed_graphs argument cannot be empty",
));
}
for named_graph_uri in named_graph_uris {
options = options.with_named_graph(named_graph_uri);
for named_graph in named_graphs.iter()? {
options = options.with_named_graph(extract_named_or_blank_node(named_graph?)?);
}
}
Ok(options)
}

@ -114,22 +114,37 @@ class TestAbstractStore(unittest.TestCase, ABC):
results = store.query(
"SELECT ?s WHERE { ?s ?p ?o }",
use_default_graph_as_union=True,
named_graph_uris=[graph],
named_graphs=[graph],
)
self.assertEqual(len(list(results)), 1)
def test_select_query_with_default_graph(self):
store = self.store()
graph_bnode = BlankNode("g")
store.add(Quad(foo, bar, baz, graph))
self.assertEqual(len(list(store.query("SELECT ?s WHERE { ?s ?p ?o }"))), 0)
store.add(Quad(foo, bar, foo))
store.add(Quad(foo, bar, bar, graph_bnode))
self.assertEqual(len(list(store.query("SELECT ?s WHERE { ?s ?p ?o }"))), 1)
results = store.query("SELECT ?s WHERE { ?s ?p ?o }", default_graph=graph)
self.assertEqual(len(list(results)), 1)
results = store.query(
"SELECT ?s WHERE { ?s ?p ?o }", default_graph_uris=[graph]
"SELECT ?s WHERE { ?s ?p ?o }",
default_graph=[DefaultGraph(), graph, graph_bnode],
)
self.assertEqual(len(list(results)), 1)
self.assertEqual(len(list(results)), 3)
def test_select_query_with_named_graph(self):
store = self.store()
graph_bnode = BlankNode("g")
store.add(Quad(foo, bar, baz, graph))
store.add(Quad(foo, bar, foo))
store.add(Quad(foo, bar, bar, graph_bnode))
store.add(Quad(foo, bar, bar, foo))
results = store.query(
"SELECT ?s WHERE { GRAPH ?g { ?s ?p ?o } }", named_graph_uris=[graph],
"SELECT ?s WHERE { GRAPH ?g { ?s ?p ?o } }",
named_graphs=[graph, graph_bnode],
)
self.assertEqual(len(list(results)), 1)
self.assertEqual(len(list(results)), 2)
def test_load_ntriples_to_default_graph(self):
store = self.store()

Loading…
Cancel
Save