diff --git a/lib/src/sparql/csv_results.rs b/lib/src/sparql/csv_results.rs index a34adc71..52879988 100644 --- a/lib/src/sparql/csv_results.rs +++ b/lib/src/sparql/csv_results.rs @@ -26,18 +26,17 @@ pub fn write_csv_results( sink.write_all(variable.as_str().as_bytes())?; } - let size = solutions.variables().len(); for solution in solutions { let solution = solution?; sink.write_all(b"\r\n")?; let mut start_binding = true; - for i in 0..size { + for value in solution.values() { if start_binding { start_binding = false; } else { sink.write_all(b",")?; } - if let Some(value) = solution.get(i) { + if let Some(value) = value { match value { Term::NamedNode(uri) => { sink.write_all(uri.as_str().as_bytes())?; @@ -102,18 +101,17 @@ pub fn write_tsv_results( sink.write_all(variable.as_str().as_bytes())?; } - let size = solutions.variables().len(); for solution in solutions { let solution = solution?; sink.write_all(b"\n")?; let mut start_binding = true; - for i in 0..size { + for value in solution.values() { if start_binding { start_binding = false; } else { sink.write_all(b"\t")?; } - if let Some(value) = solution.get(i) { + if let Some(value) = value { //TODO: full Turtle serialization sink.write_all( match value { diff --git a/lib/src/sparql/model.rs b/lib/src/sparql/model.rs index 1c7c9291..37eee595 100644 --- a/lib/src/sparql/model.rs +++ b/lib/src/sparql/model.rs @@ -321,6 +321,12 @@ impl QuerySolution { } }) } + + /// Returns an iterator over all values, bound or not + #[inline] + pub fn values(&self) -> impl Iterator> { + self.values.iter().map(|v| v.as_ref()) + } } /// A utility trait to get values for a given variable or tuple position diff --git a/python/src/sparql.rs b/python/src/sparql.rs index 181cb112..40187dab 100644 --- a/python/src/sparql.rs +++ b/python/src/sparql.rs @@ -1,13 +1,15 @@ use crate::model::*; use crate::store_utils::*; +use oxigraph::model::Term; use oxigraph::sparql::*; use pyo3::exceptions::{RuntimeError, SyntaxError, TypeError, ValueError}; use pyo3::prelude::{ - pyclass, pymethods, pyproto, FromPyObject, IntoPy, Py, PyAny, PyCell, PyErr, PyObject, + pyclass, pymethods, pyproto, FromPyObject, IntoPy, Py, PyAny, PyCell, PyErr, PyObject, PyRef, PyRefMut, PyResult, Python, }; use pyo3::{PyIterProtocol, PyMappingProtocol, PyNativeType, PyObjectProtocol}; use std::convert::TryFrom; +use std::vec::IntoIter; pub fn build_query_options( use_default_graph_as_union: bool, @@ -78,6 +80,7 @@ pub fn query_results_to_python(py: Python<'_>, results: QueryResults) -> PyResul /// It is the equivalent of a row in SQL. /// /// It could be indexes by variable name (:py:class:`Variable` or :py:class:`str`) or position in the tuple (:py:class:`int`). +/// Unpacking also works. /// /// >>> store = SledStore() /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'))) @@ -88,6 +91,9 @@ pub fn query_results_to_python(py: Python<'_>, results: QueryResults) -> PyResul /// /// >>> solution[0] /// +/// >>> s, p, o = solution +/// >>> s +/// #[pyclass(unsendable, name = QuerySolution)] pub struct PyQuerySolution { inner: QuerySolution, @@ -141,6 +147,38 @@ impl PyMappingProtocol for PyQuerySolution { } } +#[pyproto] +impl PyIterProtocol for PyQuerySolution { + fn __iter__(slf: PyRef) -> SolutionValueIter { + SolutionValueIter { + inner: slf + .inner + .values() + .map(|v| v.cloned()) + .collect::>() + .into_iter(), + } + } +} + +#[pyclass(unsendable)] +pub struct SolutionValueIter { + inner: IntoIter>, +} + +#[pyproto] +impl PyIterProtocol for SolutionValueIter { + fn __iter__(slf: PyRefMut) -> Py { + slf.into() + } + + fn __next__(mut slf: PyRefMut) -> Option> { + slf.inner + .next() + .map(|v| v.map(|v| term_to_python(slf.py(), v))) + } +} + /// An iterator of :py:class:`QuerySolution` returned by a SPARQL ``SELECT`` query /// /// >>> store = SledStore() diff --git a/python/tests/test_store.py b/python/tests/test_store.py index 99b1f0f4..87526f34 100644 --- a/python/tests/test_store.py +++ b/python/tests/test_store.py @@ -102,6 +102,8 @@ class TestAbstractStore(unittest.TestCase, ABC): self.assertEqual(solution[0], foo) self.assertEqual(solution["s"], foo) self.assertEqual(solution[Variable("s")], foo) + s, = solution + self.assertEqual(s, foo) def test_select_query_union_default_graph(self): store = self.store()