use crate::error::invalid_input_error; use crate::io::GraphSerializer; #[allow(deprecated)] use crate::io::{FileSyntax, GraphFormat}; use crate::model::*; use crate::sparql::error::EvaluationError; use crate::sparql::json_results::write_json_results; use crate::sparql::xml_results::{read_xml_results, write_xml_results}; use rand::random; use std::fmt; use std::io::{BufRead, Write}; use std::rc::Rc; /// Results of a [SPARQL query](https://www.w3.org/TR/sparql11-query/) pub enum QueryResult { /// Results of a [SELECT](https://www.w3.org/TR/sparql11-query/#select) query Solutions(QuerySolutionsIterator), /// Result of a [ASK](https://www.w3.org/TR/sparql11-query/#ask) query Boolean(bool), /// Results of a [CONSTRUCT](https://www.w3.org/TR/sparql11-query/#construct) or [DESCRIBE](https://www.w3.org/TR/sparql11-query/#describe) query Graph(QueryTriplesIterator), } impl QueryResult { pub fn read( reader: impl BufRead + 'static, format: QueryResultFormat, ) -> Result { match format { QueryResultFormat::Xml => read_xml_results(reader), QueryResultFormat::Json => Err(invalid_input_error( "JSON SPARQL results format parsing has not been implemented yet", ) .into()), //TODO: implement } } /// Writes the query results (solutions or boolean) /// /// This method fails if it is called on the `Graph` results /// /// ``` /// use oxigraph::MemoryStore; /// use oxigraph::model::*; /// use oxigraph::sparql::{QueryOptions, QueryResultFormat}; /// /// let store = MemoryStore::new(); /// let ex = NamedNode::new("http://example.com")?; /// store.insert(Quad::new(ex.clone(), ex.clone(), ex.clone(), None)); /// /// let mut results = Vec::new(); /// store.query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())?.write(&mut results, QueryResultFormat::Json)?; /// assert_eq!(results, "{\"head\":{\"vars\":[\"s\"]},\"results\":{\"bindings\":[{\"s\":{\"type\":\"uri\",\"value\":\"http://example.com\"}}]}}".as_bytes()); /// # Result::<_,Box>::Ok(()) /// ``` pub fn write( self, writer: impl Write, format: QueryResultFormat, ) -> Result<(), EvaluationError> { match format { QueryResultFormat::Xml => write_xml_results(self, writer), QueryResultFormat::Json => write_json_results(self, writer), } } /// Writes the graph query results /// /// This method fails if it is called on the `Solution` or `Boolean` results /// /// ``` /// use oxigraph::MemoryStore; /// use oxigraph::io::GraphFormat; /// use oxigraph::sparql::QueryOptions; /// use oxigraph::model::*; /// use std::io::Cursor; /// /// let graph = " .\n".as_bytes(); /// /// let store = MemoryStore::new(); /// store.load_graph(Cursor::new(graph), GraphFormat::NTriples, &GraphName::DefaultGraph, None)?; /// /// let mut results = Vec::new(); /// store.query("CONSTRUCT WHERE { ?s ?p ?o }", QueryOptions::default())?.write_graph(&mut results, GraphFormat::NTriples)?; /// assert_eq!(results, graph); /// # Result::<_,Box>::Ok(()) /// ``` pub fn write_graph( self, write: impl Write, format: GraphFormat, ) -> Result<(), EvaluationError> { if let QueryResult::Graph(triples) = self { let mut writer = GraphSerializer::from_format(format).triple_writer(write)?; for triple in triples { writer.write(&triple?)?; } writer.finish()?; Ok(()) } else { Err( invalid_input_error("Bindings or booleans could not be formatted as an RDF graph") .into(), ) } } } impl From for QueryResult { fn from(value: QuerySolutionsIterator) -> Self { QueryResult::Solutions(value) } } /// [SPARQL query](https://www.w3.org/TR/sparql11-query/) serialization formats /// /// This enumeration is non exhaustive. New formats like CSV will be added in the future. #[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] #[non_exhaustive] pub enum QueryResultFormat { /// [SPARQL Query Results XML Format](http://www.w3.org/TR/rdf-sparql-XMLres/) Xml, /// [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) Json, } impl QueryResultFormat { /// The format canonical IRI according to the [Unique URIs for file formats registry](https://www.w3.org/ns/formats/). /// /// ``` /// use oxigraph::sparql::QueryResultFormat; /// /// assert_eq!(QueryResultFormat::Json.iri(), "http://www.w3.org/ns/formats/SPARQL_Results_JSON") /// ``` pub fn iri(self) -> &'static str { match self { QueryResultFormat::Xml => "http://www.w3.org/ns/formats/SPARQL_Results_XML", QueryResultFormat::Json => "http://www.w3.org/ns/formats/SPARQL_Results_JSON", } } /// The format [IANA media type](https://tools.ietf.org/html/rfc2046). /// /// ``` /// use oxigraph::sparql::QueryResultFormat; /// /// assert_eq!(QueryResultFormat::Json.media_type(), "application/sparql-results+json") /// ``` pub fn media_type(self) -> &'static str { match self { QueryResultFormat::Xml => "application/sparql-results+xml", QueryResultFormat::Json => "application/sparql-results+json", } } /// The format [IANA-registered](https://tools.ietf.org/html/rfc2046) file extension. /// /// ``` /// use oxigraph::sparql::QueryResultFormat; /// /// assert_eq!(QueryResultFormat::Json.file_extension(), "srj") /// ``` pub fn file_extension(self) -> &'static str { match self { QueryResultFormat::Xml => "srx", QueryResultFormat::Json => "srj", } } /// Looks for a known format from a media type. /// /// It supports some media type aliases. /// For example "application/xml" is going to return `QueryResultFormat::Xml` even if it is not its canonical media type. /// /// Example: /// ``` /// use oxigraph::sparql::QueryResultFormat; /// /// assert_eq!(QueryResultFormat::from_media_type("application/sparql-results+json; charset=utf-8"), Some(QueryResultFormat::Json)) /// ``` pub fn from_media_type(media_type: &str) -> Option { if let Some(base_type) = media_type.split(';').next() { match base_type { "application/sparql-results+xml" | "application/xml" | "text/xml" => { Some(QueryResultFormat::Xml) } "application/sparql-results+json" | "application/json" | "text/json" => { Some(QueryResultFormat::Json) } _ => None, } } else { None } } } #[allow(deprecated)] impl FileSyntax for QueryResultFormat { fn iri(self) -> &'static str { self.iri() } fn media_type(self) -> &'static str { self.media_type() } fn file_extension(self) -> &'static str { self.file_extension() } fn from_mime_type(media_type: &str) -> Option { Self::from_media_type(media_type) } } /// An iterator over query result solutions /// /// ``` /// use oxigraph::MemoryStore; /// use oxigraph::sparql::{QueryResult, QueryOptions}; /// /// let store = MemoryStore::new(); /// if let QueryResult::Solutions(solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())? { /// for solution in solutions { /// println!("{:?}", solution?.get("s")); /// } /// } /// # Result::<_,Box>::Ok(()) /// ``` pub struct QuerySolutionsIterator { variables: Rc>, iter: Box>, EvaluationError>>>, } impl QuerySolutionsIterator { pub fn new( variables: Rc>, iter: Box>, EvaluationError>>>, ) -> Self { Self { variables, iter } } /// The variables used in the solutions /// /// ``` /// use oxigraph::MemoryStore; /// use oxigraph::sparql::{QueryResult, QueryOptions, Variable}; /// /// let store = MemoryStore::new(); /// if let QueryResult::Solutions(solutions) = store.query("SELECT ?s ?o WHERE { ?s ?p ?o }", QueryOptions::default())? { /// assert_eq!(solutions.variables(), &[Variable::new("s"), Variable::new("o")]); /// } /// # Result::<_,Box>::Ok(()) /// ``` pub fn variables(&self) -> &[Variable] { &*self.variables } #[deprecated(note = "Please directly use QuerySolutionsIterator as an iterator instead")] pub fn into_values_iter( self, ) -> Box>, EvaluationError>>> { self.iter } #[deprecated(note = "Please directly use QuerySolutionsIterator as an iterator instead")] pub fn destruct( self, ) -> ( Vec, Box>, EvaluationError>>>, ) { ((*self.variables).clone(), self.iter) } } impl Iterator for QuerySolutionsIterator { type Item = Result; fn next(&mut self) -> Option> { Some(self.iter.next()?.map(|values| QuerySolution { values, variables: self.variables.clone(), })) } fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } /// Tuple associating variables and terms that are the result of a SPARQL query. /// /// It is the equivalent of a row in SQL. pub struct QuerySolution { values: Vec>, variables: Rc>, } impl QuerySolution { /// Returns a value for a given position in the tuple (`usize`) or a given variable name (`&str` or `Variable`) /// /// ```ignore /// let foo = solution.get("foo"); // Get the value of the variable ?foo if it exists /// let first = solution.get(1); // Get the value of the second column if it exists /// ``` pub fn get(&self, index: impl VariableSolutionIndex) -> Option<&Term> { self.values.get(index.index(self)?).and_then(|e| e.as_ref()) } /// The number of variables which are bind pub fn len(&self) -> usize { self.values.len() } /// Is this binding empty? pub fn is_empty(&self) -> bool { self.values.is_empty() } /// Returns an iterator over bound variables pub fn iter(&self) -> impl Iterator { self.values .iter() .enumerate() .filter_map(move |(i, value)| { if let Some(value) = value { Some((&self.variables[i], value)) } else { None } }) } } /// A utility trait to get values for a given variable or tuple position pub trait VariableSolutionIndex { fn index(self, solution: &QuerySolution) -> Option; } impl VariableSolutionIndex for usize { fn index(self, _: &QuerySolution) -> Option { Some(self) } } impl VariableSolutionIndex for &str { fn index(self, solution: &QuerySolution) -> Option { solution.variables.iter().position(|v| v.as_str() == self) } } impl VariableSolutionIndex for &Variable { fn index(self, solution: &QuerySolution) -> Option { solution.variables.iter().position(|v| v == self) } } impl VariableSolutionIndex for Variable { fn index(self, solution: &QuerySolution) -> Option { (&self).index(solution) } } /// An iterator over the triples that compose a graph solution /// /// ``` /// use oxigraph::MemoryStore; /// use oxigraph::sparql::{QueryResult, QueryOptions}; /// /// let store = MemoryStore::new(); /// if let QueryResult::Graph(triples) = store.query("CONSTRUCT WHERE { ?s ?p ?o }", QueryOptions::default())? { /// for triple in triples { /// println!("{}", triple?); /// } /// } /// # Result::<_,Box>::Ok(()) /// ``` pub struct QueryTriplesIterator { pub(crate) iter: Box>>, } impl Iterator for QueryTriplesIterator { type Item = Result; fn next(&mut self) -> Option> { self.iter.next() } fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } fn fold(self, init: Acc, mut g: G) -> Acc where G: FnMut(Acc, Self::Item) -> Acc, { self.iter.fold(init, |acc, elt| g(acc, elt)) } } /// A SPARQL query variable /// /// ``` /// use oxigraph::sparql::Variable; /// /// assert_eq!( /// "?foo", /// Variable::new("foo").to_string() /// ) /// ``` #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] pub struct Variable { name: String, } impl Variable { pub fn new(name: impl Into) -> Self { Variable { name: name.into() } } pub fn as_str(&self) -> &str { &self.name } #[deprecated(note = "Please use as_str instead")] pub fn name(&self) -> Result<&str, EvaluationError> { Ok(self.as_str()) } pub fn into_string(self) -> String { self.name } pub(crate) fn new_random() -> Self { Self::new(format!("{:x}", random::())) } } impl fmt::Display for Variable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "?{}", self.name) } }