diff --git a/lib/src/sparql/algebra.rs b/lib/src/sparql/algebra.rs index 320801e7..9ce76710 100644 --- a/lib/src/sparql/algebra.rs +++ b/lib/src/sparql/algebra.rs @@ -2,12 +2,150 @@ use crate::model::*; use crate::sparql::model::*; +use crate::sparql::parser::{parse_query, parse_update, ParseError}; use oxiri::Iri; use rio_api::model as rio; use std::collections::BTreeSet; +use std::convert::TryFrom; use std::fmt; -use std::ops::Add; use std::rc::Rc; +use std::str::FromStr; + +/// A parsed [SPARQL query](https://www.w3.org/TR/sparql11-query/) +/// +/// ``` +/// use oxigraph::model::NamedNode; +/// use oxigraph::sparql::Query; +/// +/// let query_str = "SELECT ?s ?p ?o WHERE { ?s ?p ?o . }"; +/// let mut query = Query::parse(query_str, None)?; +/// +/// assert_eq!(query.to_string(), query_str); +/// +/// // We edit the query dataset specification +/// query.dataset_mut().set_default_graph(vec![NamedNode::new("http://example.com").unwrap().into()]); +/// assert_eq!(query.to_string(), "SELECT ?s ?p ?o FROM WHERE { ?s ?p ?o . }"); +/// # Result::Ok::<_, Box>(()) +/// ``` +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub struct Query(pub(crate) QueryVariants); + +impl Query { + /// Parses a SPARQL query with an optional base IRI to resolve relative IRIs in the query + pub fn parse(query: &str, base_iri: Option<&str>) -> Result { + parse_query(query, base_iri) + } + + /// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) + pub fn dataset(&self) -> &QueryDataset { + match &self.0 { + QueryVariants::Select { dataset, .. } => dataset, + QueryVariants::Construct { dataset, .. } => dataset, + QueryVariants::Describe { dataset, .. } => dataset, + QueryVariants::Ask { dataset, .. } => dataset, + } + } + + /// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) + pub fn dataset_mut(&mut self) -> &mut QueryDataset { + match &mut self.0 { + QueryVariants::Select { dataset, .. } => dataset, + QueryVariants::Construct { dataset, .. } => dataset, + QueryVariants::Describe { dataset, .. } => dataset, + QueryVariants::Ask { dataset, .. } => dataset, + } + } +} + +impl fmt::Display for Query { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl FromStr for Query { + type Err = ParseError; + + fn from_str(query: &str) -> Result { + Self::parse(query, None) + } +} + +impl<'a> TryFrom<&'a str> for Query { + type Error = ParseError; + + fn try_from(query: &str) -> Result { + Self::from_str(query) + } +} + +impl<'a> TryFrom<&'a String> for Query { + type Error = ParseError; + + fn try_from(query: &String) -> Result { + Self::from_str(query) + } +} + +/// A parsed [SPARQL update](https://www.w3.org/TR/sparql11-update/) +/// +/// ``` +/// use oxigraph::sparql::Update; +/// +/// let update_str = "CLEAR ALL ;"; +/// let update = Update::parse(update_str, None)?; +/// +/// assert_eq!(update.to_string().trim(), update_str); +/// # Result::Ok::<_, oxigraph::sparql::ParseError>(()) +/// ``` +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub struct Update { + pub(crate) base_iri: Option>>, + pub(crate) operations: Vec, +} + +impl Update { + /// Parses a SPARQL update with an optional base IRI to resolve relative IRIs in the query + pub fn parse(update: &str, base_iri: Option<&str>) -> Result { + parse_update(update, base_iri) + } +} + +impl fmt::Display for Update { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(base_iri) = &self.base_iri { + writeln!(f, "BASE <{}>", base_iri)?; + } + for update in &self.operations { + writeln!(f, "{} ;", update)?; + } + Ok(()) + } +} + +impl FromStr for Update { + type Err = ParseError; + + fn from_str(update: &str) -> Result { + Self::parse(update, None) + } +} + +impl<'a> TryFrom<&'a str> for Update { + type Error = ParseError; + + fn try_from(update: &str) -> Result { + Self::from_str(update) + } +} + +impl<'a> TryFrom<&'a String> for Update { + type Error = ParseError; + + fn try_from(update: &String) -> Result { + Self::from_str(update) + } +} #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub enum NamedNodeOrVariable { @@ -986,7 +1124,7 @@ impl<'a> fmt::Display for SparqlGraphPattern<'a> { "{{ {} }}", SparqlGraphRootPattern { algebra: p, - dataset: &EMPTY_DATASET + dataset: &QueryDataset::default() } ), } @@ -995,7 +1133,7 @@ impl<'a> fmt::Display for SparqlGraphPattern<'a> { struct SparqlGraphRootPattern<'a> { algebra: &'a GraphPattern, - dataset: &'a DatasetSpec, + dataset: &'a QueryDataset, } impl<'a> fmt::Display for SparqlGraphRootPattern<'a> { @@ -1041,7 +1179,7 @@ impl<'a> fmt::Display for SparqlGraphRootPattern<'a> { } write!( f, - "{} {} WHERE {{ {} }}", + "{}{} WHERE {{ {} }}", build_sparql_select_arguments(project), self.dataset, SparqlGraphPattern(p) @@ -1304,79 +1442,130 @@ impl<'a> fmt::Display for SparqlOrderComparator<'a> { } } -#[derive(Eq, PartialEq, Debug, Clone, Hash, Default)] -pub struct DatasetSpec { - pub default: Vec, - pub named: Vec, +/// A SPARQL query [dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub struct QueryDataset { + default: Option>, + named: Option>, } -impl DatasetSpec { - pub fn new_with_default(graph: NamedNode) -> Self { - Self { - default: vec![graph], - named: Vec::default(), - } - } - - pub fn new_with_named(graph: NamedNode) -> Self { +impl Default for QueryDataset { + fn default() -> Self { Self { - default: Vec::default(), - named: vec![graph], + default: Some(vec![GraphName::DefaultGraph]), + named: None, } } - - pub fn is_empty(&self) -> bool { - self.default.is_empty() && self.named.is_empty() - } } -impl Add for DatasetSpec { - type Output = Self; - - fn add(mut self, rhs: Self) -> Self { - self.default.extend_from_slice(&rhs.default); - self.named.extend_from_slice(&rhs.named); - self - } -} - -impl fmt::Display for DatasetSpec { +impl QueryDataset { + /// Checks if this dataset specification is the default one + /// (i.e. the default graph is the store default graph and all the store named graphs are available) + /// + /// ``` + /// use oxigraph::sparql::Query; + /// + /// assert!(Query::parse("SELECT ?s ?p ?o WHERE { ?s ?p ?o . }", None)?.dataset().is_default_dataset()); + /// assert!(!Query::parse("SELECT ?s ?p ?o FROM WHERE { ?s ?p ?o . }", None)?.dataset().is_default_dataset()); + /// + /// # Result::Ok::<_, Box>(()) + /// ``` + pub fn is_default_dataset(&self) -> bool { + self.default + .as_ref() + .map_or(false, |t| t == &[GraphName::DefaultGraph]) + && self.named.is_none() + } + + /// Returns the list of the store graphs that are available to the query as the default graph or `None` if the union of all graphs is used as the default graph + /// This list is by default only the store default graph + pub fn default_graph_graphs(&self) -> Option<&[GraphName]> { + self.default.as_deref() + } + + /// Sets if the default graph for the query should be the union of all the graphs in the queried store + pub fn set_default_graph_as_union(&mut self) { + self.default = None; + } + + /// Sets the list of graphs the query should consider as being part of the default graph. + /// + /// By default only the store default graph is considered. + /// ``` + /// use oxigraph::model::NamedNode; + /// use oxigraph::sparql::Query; + /// + /// let mut query = Query::parse("SELECT ?s ?p ?o WHERE { ?s ?p ?o . }", None)?; + /// query.dataset_mut().set_default_graph(vec![NamedNode::new("http://example.com")?.into()]); + /// assert_eq!(query.to_string(), "SELECT ?s ?p ?o FROM WHERE { ?s ?p ?o . }"); + /// + /// # Result::Ok::<_, Box>(()) + /// ``` + pub fn set_default_graph(&mut self, graphs: Vec) { + self.default = Some(graphs) + } + + /// Returns the list of the available named graphs for the query or `None` if all graphs are available + pub fn available_named_graphs(&self) -> Option<&[NamedOrBlankNode]> { + self.named.as_deref() + } + + /// Sets the list of allowed named graphs in the query. + /// + /// ``` + /// use oxigraph::model::NamedNode; + /// use oxigraph::sparql::Query; + /// + /// let mut query = Query::parse("SELECT ?s ?p ?o WHERE { ?s ?p ?o . }", None)?; + /// query.dataset_mut().set_available_named_graphs(vec![NamedNode::new("http://example.com")?.into()]); + /// assert_eq!(query.to_string(), "SELECT ?s ?p ?o FROM NAMED WHERE { ?s ?p ?o . }"); + /// + /// # Result::Ok::<_, Box>(()) + /// ``` + pub fn set_available_named_graphs(&mut self, named_graphs: Vec) { + self.named = Some(named_graphs); + } +} + +impl fmt::Display for QueryDataset { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for g in &self.default { - write!(f, "FROM {} ", g)?; + //TODO: does not encode everything + if let Some(graphs) = &self.default { + for g in graphs { + if !g.is_default_graph() { + write!(f, " FROM {}", g)?; + } + } } - for g in &self.named { - write!(f, "FROM NAMED {} ", g)?; + if let Some(graphs) = &self.named { + for g in graphs { + write!(f, " FROM NAMED {}", g)?; + } } Ok(()) } } -const EMPTY_DATASET: DatasetSpec = DatasetSpec { - default: Vec::new(), - named: Vec::new(), -}; - #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub enum QueryVariants { Select { - dataset: Rc, + dataset: QueryDataset, algebra: Rc, base_iri: Option>>, }, Construct { construct: Rc>, - dataset: Rc, + dataset: QueryDataset, algebra: Rc, base_iri: Option>>, }, Describe { - dataset: Rc, + dataset: QueryDataset, algebra: Rc, base_iri: Option>>, }, Ask { - dataset: Rc, + dataset: QueryDataset, algebra: Rc, base_iri: Option>>, }, @@ -1410,11 +1599,11 @@ impl fmt::Display for QueryVariants { } write!( f, - "}} {} WHERE {{ {} }}", + "}}{} WHERE {{ {} }}", dataset, SparqlGraphRootPattern { algebra, - dataset: &EMPTY_DATASET + dataset: &QueryDataset::default() } ) } @@ -1428,11 +1617,11 @@ impl fmt::Display for QueryVariants { } write!( f, - "DESCRIBE * {} WHERE {{ {} }}", + "DESCRIBE *{} WHERE {{ {} }}", dataset, SparqlGraphRootPattern { algebra, - dataset: &EMPTY_DATASET + dataset: &QueryDataset::default() } ) } @@ -1446,11 +1635,11 @@ impl fmt::Display for QueryVariants { } write!( f, - "ASK {} WHERE {{ {} }}", + "ASK{} WHERE {{ {} }}", dataset, SparqlGraphRootPattern { algebra, - dataset: &EMPTY_DATASET + dataset: &QueryDataset::default() } ) } @@ -1469,7 +1658,7 @@ pub enum GraphUpdateOperation { DeleteInsert { delete: Vec, insert: Vec, - using: DatasetSpec, + using: QueryDataset, algebra: GraphPattern, }, /// [load](https://www.w3.org/TR/sparql11-update/#def_loadoperation) @@ -1523,18 +1712,24 @@ impl fmt::Display for GraphUpdateOperation { } writeln!(f, "}}")?; } - for g in &using.default { - writeln!(f, "USING {}", g)?; + if let Some(using_default) = using.default_graph_graphs() { + for g in using_default { + if !g.is_default_graph() { + writeln!(f, "USING {}", g)?; + } + } } - for g in &using.named { - writeln!(f, "USING NAMED {}", g)?; + if let Some(using_named) = using.available_named_graphs() { + for g in using_named { + writeln!(f, "USING NAMED {}", g)?; + } } write!( f, "WHERE {{ {} }}", SparqlGraphRootPattern { algebra, - dataset: &DatasetSpec::default() + dataset: &QueryDataset::default() } ) } diff --git a/lib/src/sparql/dataset.rs b/lib/src/sparql/dataset.rs index 4caaf255..4a4e5b08 100644 --- a/lib/src/sparql/dataset.rs +++ b/lib/src/sparql/dataset.rs @@ -1,5 +1,4 @@ -use crate::model::{GraphName, NamedOrBlankNode}; -use crate::sparql::algebra::DatasetSpec; +use crate::sparql::algebra::QueryDataset; use crate::sparql::EvaluationError; use crate::store::numeric_encoder::{ EncodedQuad, EncodedTerm, ReadEncoder, StrContainer, StrEncodingAware, StrId, StrLookup, @@ -12,61 +11,45 @@ use std::iter::empty; pub(crate) struct DatasetView { store: S, extra: RefCell, - default_graph_as_union: bool, - dataset: Option>, + dataset: EncodedDatasetSpec, } impl DatasetView { - pub fn new( - store: S, - default_graph_as_union: bool, - default_graphs: &[GraphName], - named_graphs: &[NamedOrBlankNode], - dataset: &DatasetSpec, - ) -> Result { - 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::, _>>() - .map_err(|e| e.into())?, - named: named_graphs - .iter() - .flat_map(|g| { - store - .get_encoded_named_or_blank_node(g.as_ref()) - .transpose() - }) - .collect::, _>>() - .map_err(|e| e.into())?, - }) - } else if dataset.is_empty() { - None - } else { - Some(EncodedDatasetSpec { - default: dataset - .default - .iter() - .flat_map(|g| store.get_encoded_named_node(g.as_ref()).transpose()) - .collect::, _>>() - .map_err(|e| e.into())?, - named: dataset - .named - .iter() - .flat_map(|g| store.get_encoded_named_node(g.as_ref()).transpose()) - .collect::, _>>() - .map_err(|e| e.into())?, - }) + pub fn new(store: S, dataset: &QueryDataset) -> Result { + let dataset = EncodedDatasetSpec { + default: dataset + .default_graph_graphs() + .map(|graphs| { + graphs + .iter() + .flat_map(|g| store.get_encoded_graph_name(g.as_ref()).transpose()) + .collect::, _>>() + }) + .transpose() + .map_err(|e| e.into())?, + named: dataset + .available_named_graphs() + .map(|graphs| { + graphs + .iter() + .flat_map(|g| { + store + .get_encoded_named_or_blank_node(g.as_ref()) + .transpose() + }) + .collect::, _>>() + }) + .transpose() + .map_err(|e| e.into())?, }; Ok(Self { store, extra: RefCell::new(Rodeo::default()), - default_graph_as_union, dataset, }) } + #[allow(clippy::needless_collect)] fn encoded_quads_for_pattern_in_dataset( &self, subject: Option>, @@ -75,56 +58,85 @@ impl DatasetView { graph_name: Option>, ) -> Box>, EvaluationError>>> { - if let Some(dataset) = &self.dataset { - if let Some(graph_name) = graph_name { - if graph_name == EncodedTerm::DefaultGraph { - let iters = dataset - .default - .iter() - .map(|graph_name| { - self.store.encoded_quads_for_pattern( + if let Some(graph_name) = graph_name { + if graph_name.is_default_graph() { + if let Some(default_graph_graphs) = &self.dataset.default { + if default_graph_graphs.len() == 1 { + // Single graph optimization + Box::new( + map_iter(self.store.encoded_quads_for_pattern( subject, predicate, object, - Some(*graph_name), - ) - }) - .collect::>(); - Box::new(map_iter(iters.into_iter().flatten()).map(|quad| { - let quad = quad?; - Ok(EncodedQuad::new( - quad.subject, - quad.predicate, - quad.object, - EncodedTerm::DefaultGraph, - )) - })) - } else if dataset.named.contains(&graph_name) { - Box::new(map_iter(self.store.encoded_quads_for_pattern( - subject, - predicate, - object, - Some(graph_name), - ))) + Some(default_graph_graphs[0]), + )) + .map(|quad| { + let quad = quad?; + Ok(EncodedQuad::new( + quad.subject, + quad.predicate, + quad.object, + EncodedTerm::DefaultGraph, + )) + }), + ) + } else { + let iters = default_graph_graphs + .iter() + .map(|graph_name| { + self.store.encoded_quads_for_pattern( + subject, + predicate, + object, + Some(*graph_name), + ) + }) + .collect::>(); + Box::new(map_iter(iters.into_iter().flatten()).map(|quad| { + let quad = quad?; + Ok(EncodedQuad::new( + quad.subject, + quad.predicate, + quad.object, + EncodedTerm::DefaultGraph, + )) + })) + } } else { - Box::new(empty()) + Box::new(map_iter( + self.store + .encoded_quads_for_pattern(subject, predicate, object, None), + )) } + } else if self + .dataset + .named + .as_ref() + .map_or(true, |d| d.contains(&graph_name)) + { + Box::new(map_iter(self.store.encoded_quads_for_pattern( + subject, + predicate, + object, + Some(graph_name), + ))) } else { - let iters = dataset - .named - .iter() - .map(|graph_name| { - self.store.encoded_quads_for_pattern( - subject, - predicate, - object, - Some(*graph_name), - ) - }) - .collect::>(); - Box::new(map_iter(iters.into_iter().flatten())) + Box::new(empty()) } - } else if graph_name == None { + } else if let Some(named_graphs) = &self.dataset.named { + let iters = named_graphs + .iter() + .map(|graph_name| { + self.store.encoded_quads_for_pattern( + subject, + predicate, + object, + Some(*graph_name), + ) + }) + .collect::>(); + Box::new(map_iter(iters.into_iter().flatten())) + } else { Box::new( map_iter( self.store @@ -135,10 +147,6 @@ impl DatasetView { Ok(quad) => quad.graph_name != EncodedTerm::DefaultGraph, }), ) - } else { - Box::new(map_iter(self.store.encoded_quads_for_pattern( - subject, predicate, object, graph_name, - ))) } } } @@ -186,21 +194,7 @@ impl ReadableEncodedStore for DatasetView { 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) - } + self.encoded_quads_for_pattern_in_dataset(subject, predicate, object, graph_name) } else { Box::new(empty()) } @@ -278,6 +272,6 @@ pub enum DatasetStrId { impl StrId for DatasetStrId {} struct EncodedDatasetSpec { - default: Vec>, - named: Vec>, + default: Option>>, + named: Option>>, } diff --git a/lib/src/sparql/eval.rs b/lib/src/sparql/eval.rs index d1d04263..341704b1 100644 --- a/lib/src/sparql/eval.rs +++ b/lib/src/sparql/eval.rs @@ -2,10 +2,9 @@ use crate::model::vocab::{rdf, xsd}; use crate::model::xsd::*; use crate::model::Triple; use crate::model::{BlankNode, LiteralRef, NamedNodeRef}; -use crate::sparql::algebra::{DatasetSpec, GraphPattern, QueryVariants}; +use crate::sparql::algebra::{GraphPattern, Query, QueryDataset, QueryVariants}; use crate::sparql::error::EvaluationError; use crate::sparql::model::*; -use crate::sparql::parser::Query; use crate::sparql::plan::*; use crate::sparql::service::ServiceHandler; use crate::store::numeric_encoder::*; @@ -527,7 +526,7 @@ where .ok_or_else(|| EvaluationError::msg("The SERVICE name is not bound"))?, )?, Query(QueryVariants::Select { - dataset: Rc::new(DatasetSpec::default()), + dataset: QueryDataset::default(), algebra: graph_pattern, base_iri: self.base_iri.clone(), }), diff --git a/lib/src/sparql/mod.rs b/lib/src/sparql/mod.rs index 899362db..6b36ad21 100644 --- a/lib/src/sparql/mod.rs +++ b/lib/src/sparql/mod.rs @@ -17,8 +17,8 @@ mod service; mod update; mod xml_results; -use crate::model::{GraphName, NamedOrBlankNode}; use crate::sparql::algebra::QueryVariants; +pub use crate::sparql::algebra::{Query, Update}; use crate::sparql::dataset::DatasetView; pub use crate::sparql::error::EvaluationError; use crate::sparql::eval::SimpleEvaluator; @@ -29,7 +29,6 @@ pub use crate::sparql::model::QuerySolutionIter; pub use crate::sparql::model::QueryTripleIter; pub use crate::sparql::model::{Variable, VariableNameParseError}; pub use crate::sparql::parser::ParseError; -pub use crate::sparql::parser::{Query, Update}; use crate::sparql::plan::{PlanNode, TripleTemplate}; use crate::sparql::plan_builder::PlanBuilder; pub use crate::sparql::service::ServiceHandler; @@ -80,13 +79,7 @@ impl SimplePreparedQuery { base_iri, dataset, } => { - let dataset = Rc::new(DatasetView::new( - store, - options.default_graph_as_union, - &options.default_graphs, - &options.named_graphs, - &dataset, - )?); + let dataset = Rc::new(DatasetView::new(store, &dataset)?); let (plan, variables) = PlanBuilder::build(dataset.as_ref(), &algebra)?; SimplePreparedQueryAction::Select { plan: Rc::new(plan), @@ -99,13 +92,7 @@ impl SimplePreparedQuery { base_iri, dataset, } => { - let dataset = Rc::new(DatasetView::new( - store, - options.default_graph_as_union, - &options.default_graphs, - &options.named_graphs, - &dataset, - )?); + let dataset = Rc::new(DatasetView::new(store, &dataset)?); let (plan, _) = PlanBuilder::build(dataset.as_ref(), &algebra)?; SimplePreparedQueryAction::Ask { plan: Rc::new(plan), @@ -118,13 +105,7 @@ impl SimplePreparedQuery { base_iri, dataset, } => { - let dataset = Rc::new(DatasetView::new( - store, - options.default_graph_as_union, - &options.default_graphs, - &options.named_graphs, - &dataset, - )?); + let dataset = Rc::new(DatasetView::new(store, &dataset)?); let (plan, variables) = PlanBuilder::build(dataset.as_ref(), &algebra)?; SimplePreparedQueryAction::Construct { plan: Rc::new(plan), @@ -141,13 +122,7 @@ impl SimplePreparedQuery { base_iri, dataset, } => { - let dataset = Rc::new(DatasetView::new( - store, - options.default_graph_as_union, - &options.default_graphs, - &options.named_graphs, - &dataset, - )?); + let dataset = Rc::new(DatasetView::new(store, &dataset)?); let (plan, _) = PlanBuilder::build(dataset.as_ref(), &algebra)?; SimplePreparedQueryAction::Describe { plan: Rc::new(plan), @@ -181,9 +156,6 @@ impl SimplePreparedQuery { /// Options for SPARQL query evaluation #[derive(Clone)] pub struct QueryOptions { - pub(crate) default_graph_as_union: bool, - pub(crate) default_graphs: Vec, - pub(crate) named_graphs: Vec, pub(crate) service_handler: Rc>, } @@ -191,38 +163,12 @@ impl Default for QueryOptions { #[inline] fn default() -> Self { Self { - default_graph_as_union: false, - default_graphs: Vec::new(), - named_graphs: Vec::new(), service_handler: Rc::new(EmptyServiceHandler), } } } impl QueryOptions { - /// Consider the union of all graphs in the store as the default graph - #[inline] - pub fn with_default_graph_as_union(mut self) -> Self { - self.default_graph_as_union = true; - self - } - - /// 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) -> 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) -> Self { - self.named_graphs.push(named_graph_name.into()); - self - } - /// Use a simple HTTP 1.1 client built into Oxigraph to execute [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE calls. /// /// Requires the `"http_client"` optional feature. diff --git a/lib/src/sparql/parser.rs b/lib/src/sparql/parser.rs index 3e659169..8a60d193 100644 --- a/lib/src/sparql/parser.rs +++ b/lib/src/sparql/parser.rs @@ -8,167 +8,67 @@ use peg::parser; use peg::str::LineCol; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; -use std::convert::TryFrom; use std::error::Error; use std::rc::Rc; use std::str::Chars; use std::str::FromStr; use std::{char, fmt}; -/// A parsed [SPARQL query](https://www.w3.org/TR/sparql11-query/) -/// -/// ``` -/// use oxigraph::sparql::Query; -/// -/// let query_str = "SELECT ?s ?p ?o WHERE { ?s ?p ?o . }"; -/// let query = Query::parse(query_str, None)?; -/// -/// assert_eq!(query.to_string(), query_str); -/// # Result::Ok::<_, oxigraph::sparql::ParseError>(()) -/// ``` -#[derive(Eq, PartialEq, Debug, Clone, Hash)] -pub struct Query(pub(crate) QueryVariants); - -impl fmt::Display for Query { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Query { - /// Parses a SPARQL query with an optional base IRI to resolve relative IRIs in the query - pub fn parse(query: &str, base_iri: Option<&str>) -> Result { - let mut state = ParserState { - base_iri: if let Some(base_iri) = base_iri { - Some(Rc::new(Iri::parse(base_iri.to_owned()).map_err(|e| { - ParseError { - inner: ParseErrorKind::InvalidBaseIri(e), - } - })?)) - } else { - None - }, - namespaces: HashMap::default(), - used_bnodes: HashSet::default(), - currently_used_bnodes: HashSet::default(), - aggregations: Vec::default(), - }; - - Ok(Self( - parser::QueryUnit(&unescape_unicode_codepoints(query), &mut state).map_err(|e| { +/// Parses a SPARQL query with an optional base IRI to resolve relative IRIs in the query +pub fn parse_query(query: &str, base_iri: Option<&str>) -> Result { + let mut state = ParserState { + base_iri: if let Some(base_iri) = base_iri { + Some(Rc::new(Iri::parse(base_iri.to_owned()).map_err(|e| { ParseError { - inner: ParseErrorKind::Parser(e), + inner: ParseErrorKind::InvalidBaseIri(e), } - })?, - )) - } -} - -impl FromStr for Query { - type Err = ParseError; - - fn from_str(query: &str) -> Result { - Self::parse(query, None) - } -} - -impl<'a> TryFrom<&'a str> for Query { - type Error = ParseError; - - fn try_from(query: &str) -> Result { - Self::from_str(query) - } -} - -impl<'a> TryFrom<&'a String> for Query { - type Error = ParseError; - - fn try_from(query: &String) -> Result { - Self::from_str(query) - } -} - -/// A parsed [SPARQL update](https://www.w3.org/TR/sparql11-update/) -/// -/// ``` -/// use oxigraph::sparql::Update; -/// -/// let update_str = "CLEAR ALL ;"; -/// let update = Update::parse(update_str, None)?; -/// -/// assert_eq!(update.to_string().trim(), update_str); -/// # Result::Ok::<_, oxigraph::sparql::ParseError>(()) -/// ``` -#[derive(Eq, PartialEq, Debug, Clone, Hash)] -pub struct Update { - pub(crate) base_iri: Option>>, - pub(crate) operations: Vec, -} - -impl fmt::Display for Update { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(base_iri) = &self.base_iri { - writeln!(f, "BASE <{}>", base_iri)?; - } - for update in &self.operations { - writeln!(f, "{} ;", update)?; - } - Ok(()) - } -} - -impl Update { - /// Parses a SPARQL update with an optional base IRI to resolve relative IRIs in the query - pub fn parse(update: &str, base_iri: Option<&str>) -> Result { - let mut state = ParserState { - base_iri: if let Some(base_iri) = base_iri { - Some(Rc::new(Iri::parse(base_iri.to_owned()).map_err(|e| { - ParseError { - inner: ParseErrorKind::InvalidBaseIri(e), - } - })?)) - } else { - None - }, - namespaces: HashMap::default(), - used_bnodes: HashSet::default(), - currently_used_bnodes: HashSet::default(), - aggregations: Vec::default(), - }; - - let operations = parser::UpdateInit(&unescape_unicode_codepoints(update), &mut state) - .map_err(|e| ParseError { + })?)) + } else { + None + }, + namespaces: HashMap::default(), + used_bnodes: HashSet::default(), + currently_used_bnodes: HashSet::default(), + aggregations: Vec::default(), + }; + + Ok(Query( + parser::QueryUnit(&unescape_unicode_codepoints(query), &mut state).map_err(|e| { + ParseError { inner: ParseErrorKind::Parser(e), - })?; - Ok(Self { - operations, - base_iri: state.base_iri, - }) - } -} - -impl FromStr for Update { - type Err = ParseError; - - fn from_str(update: &str) -> Result { - Self::parse(update, None) - } -} - -impl<'a> TryFrom<&'a str> for Update { - type Error = ParseError; - - fn try_from(update: &str) -> Result { - Self::from_str(update) - } + } + })?, + )) } -impl<'a> TryFrom<&'a String> for Update { - type Error = ParseError; - - fn try_from(update: &String) -> Result { - Self::from_str(update) - } +/// Parses a SPARQL update with an optional base IRI to resolve relative IRIs in the query +pub fn parse_update(update: &str, base_iri: Option<&str>) -> Result { + let mut state = ParserState { + base_iri: if let Some(base_iri) = base_iri { + Some(Rc::new(Iri::parse(base_iri.to_owned()).map_err(|e| { + ParseError { + inner: ParseErrorKind::InvalidBaseIri(e), + } + })?)) + } else { + None + }, + namespaces: HashMap::default(), + used_bnodes: HashSet::default(), + currently_used_bnodes: HashSet::default(), + aggregations: Vec::default(), + }; + + let operations = + parser::UpdateInit(&unescape_unicode_codepoints(update), &mut state).map_err(|e| { + ParseError { + inner: ParseErrorKind::Parser(e), + } + })?; + Ok(Update { + operations, + base_iri: state.base_iri, + }) } /// Error returned during SPARQL parsing. @@ -488,7 +388,7 @@ fn copy_graph( Variable::new_unchecked("o"), to.into(), )], - using: DatasetSpec::default(), + using: QueryDataset::default(), algebra: match from { NamedOrDefaultGraphTarget::NamedNode(from) => { GraphPattern::Graph(from.into(), Box::new(bgp)) @@ -771,7 +671,7 @@ parser! { //[7] rule SelectQuery() -> QueryVariants = s:SelectClause() _ d:DatasetClauses() _ w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { QueryVariants::Select { - dataset: Rc::new(d), + dataset: d, algebra: Rc::new(build_select(s, w, g, h, o, l, v, state)), base_iri: state.base_iri.clone() } @@ -808,7 +708,7 @@ parser! { i("CONSTRUCT") _ c:ConstructTemplate() _ d:DatasetClauses() _ w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { QueryVariants::Construct { construct: Rc::new(c), - dataset: Rc::new(d), + dataset: d, algebra: Rc::new(build_select(Selection::default(), w, g, h, o, l, v, state)), base_iri: state.base_iri.clone() } @@ -816,7 +716,7 @@ parser! { i("CONSTRUCT") _ d:DatasetClauses() _ i("WHERE") _ "{" _ c:ConstructQuery_optional_triple_template() _ "}" _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { QueryVariants::Construct { construct: Rc::new(c.clone()), - dataset: Rc::new(d), + dataset: d, algebra: Rc::new(build_select( Selection::default(), GraphPattern::BGP(c.into_iter().map(TripleOrPathPattern::from).collect()), @@ -832,14 +732,14 @@ parser! { rule DescribeQuery() -> QueryVariants = i("DESCRIBE") _ "*" _ d:DatasetClauses() w:WhereClause()? _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { QueryVariants::Describe { - dataset: Rc::new(d), + dataset: d, algebra: Rc::new(build_select(Selection::default(), w.unwrap_or_else(GraphPattern::default), g, h, o, l, v, state)), base_iri: state.base_iri.clone() } } / i("DESCRIBE") _ p:DescribeQuery_item()+ _ d:DatasetClauses() w:WhereClause()? _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { QueryVariants::Describe { - dataset: Rc::new(d), + dataset: d, algebra: Rc::new(build_select(Selection { option: SelectionOption::Default, variables: Some(p.into_iter().map(|var_or_iri| match var_or_iri { @@ -855,27 +755,41 @@ parser! { //[12] rule AskQuery() -> QueryVariants = i("ASK") _ d:DatasetClauses() w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { QueryVariants::Ask { - dataset: Rc::new(d), + dataset: d, algebra: Rc::new(build_select(Selection::default(), w, g, h, o, l, v, state)), base_iri: state.base_iri.clone() } } //[13] - rule DatasetClause() -> DatasetSpec = i("FROM") _ d:(DefaultGraphClause() / NamedGraphClause()) { d } - rule DatasetClauses() -> DatasetSpec = d:DatasetClauses_item()* { - d.into_iter().fold(DatasetSpec::default(), |mut a, b| a + b) + rule DatasetClause() -> (Option, Option) = i("FROM") _ d:(DefaultGraphClause() / NamedGraphClause()) { d } + rule DatasetClauses() -> QueryDataset = d:DatasetClause() ** (_) { + let mut dataset = QueryDataset::default(); + if !d.is_empty() { + let mut default = Vec::new(); + let mut named = Vec::new(); + for (d, n) in d { + if let Some(d) = d { + default.push(d); + } + if let Some(n) = n { + named.push(n); + } + } + dataset.set_default_graph(default); + dataset.set_available_named_graphs(named); + } + dataset } - rule DatasetClauses_item() -> DatasetSpec = d:DatasetClause() _ { d } //[14] - rule DefaultGraphClause() -> DatasetSpec = s:SourceSelector() { - DatasetSpec::new_with_default(s) + rule DefaultGraphClause() -> (Option, Option) = s:SourceSelector() { + (Some(s.into()), None) } //[15] - rule NamedGraphClause() -> DatasetSpec = i("NAMED") _ s:SourceSelector() { - DatasetSpec::new_with_named(s) + rule NamedGraphClause() -> (Option, Option) = i("NAMED") _ s:SourceSelector() { + (None, Some(s.into())) } //[16] @@ -1034,13 +948,13 @@ parser! { vec![GraphUpdateOperation::DeleteInsert { delete: d, insert: Vec::new(), - using: DatasetSpec::default(), + using: QueryDataset::default(), algebra }] } //[41] - rule Modify() -> Vec = with:Modify_with()? _ Modify_clear() c:Modify_clauses() _ using:(UsingClause() ** (_)) _ i("WHERE") _ algebra:GroupGraphPattern() { + rule Modify() -> Vec = with:Modify_with()? _ Modify_clear() c:Modify_clauses() _ u:(UsingClause() ** (_)) _ i("WHERE") _ algebra:GroupGraphPattern() { let (delete, insert) = c; let mut delete = delete.unwrap_or_else(Vec::new); let mut insert = insert.unwrap_or_else(Vec::new); @@ -1061,10 +975,26 @@ parser! { algebra = GraphPattern::Graph(with.into(), Box::new(algebra)); } + let mut using = QueryDataset::default(); + if !u.is_empty() { + let mut using_default = Vec::new(); + let mut using_named = Vec::new(); + for (d, n) in u { + if let Some(d) = d { + using_default.push(d) + } + if let Some(n) = n { + using_named.push(n) + } + } + using.set_default_graph(using_default); + using.set_available_named_graphs(using_named); + } + vec![GraphUpdateOperation::DeleteInsert { delete, insert, - using: using.into_iter().fold(DatasetSpec::default(), |mut a, b| a + b), + using, algebra }] } @@ -1086,12 +1016,12 @@ parser! { rule InsertClause() -> Vec = i("INSERT") _ q:QuadPattern() { q } //[44] - rule UsingClause() -> DatasetSpec = i("USING") _ d:(UsingClause_default() / UsingClause_named()) { d } - rule UsingClause_default() -> DatasetSpec = i:iri() { - DatasetSpec::new_with_default(i) + rule UsingClause() -> (Option, Option) = i("USING") _ d:(UsingClause_default() / UsingClause_named()) { d } + rule UsingClause_default() -> (Option, Option) = i:iri() { + (Some(i.into()), None) } - rule UsingClause_named() -> DatasetSpec = i("NAMED") _ i:iri() { - DatasetSpec::new_with_named(i) + rule UsingClause_named() -> (Option, Option) = i("NAMED") _ i:iri() { + (None, Some(i.into())) } //[45] diff --git a/lib/src/sparql/service.rs b/lib/src/sparql/service.rs index f2631dc5..54d0c92d 100644 --- a/lib/src/sparql/service.rs +++ b/lib/src/sparql/service.rs @@ -1,9 +1,9 @@ use crate::error::{invalid_data_error, invalid_input_error}; use crate::model::NamedNode; +use crate::sparql::algebra::Query; use crate::sparql::error::EvaluationError; use crate::sparql::http::Client; use crate::sparql::model::QueryResults; -use crate::sparql::parser::Query; use crate::sparql::QueryResultsFormat; use http::header::{ACCEPT, CONTENT_TYPE, USER_AGENT}; use http::{Method, Request, StatusCode}; diff --git a/lib/src/sparql/update.rs b/lib/src/sparql/update.rs index f203eba8..a2590140 100644 --- a/lib/src/sparql/update.rs +++ b/lib/src/sparql/update.rs @@ -2,8 +2,8 @@ use crate::error::{invalid_data_error, invalid_input_error}; use crate::io::GraphFormat; use crate::model::{BlankNode, GraphNameRef, NamedNode, Term}; use crate::sparql::algebra::{ - DatasetSpec, GraphPattern, GraphTarget, GraphUpdateOperation, NamedNodeOrVariable, QuadPattern, - TermOrVariable, + GraphPattern, GraphTarget, GraphUpdateOperation, NamedNodeOrVariable, QuadPattern, + QueryDataset, TermOrVariable, }; use crate::sparql::dataset::{DatasetStrId, DatasetView}; use crate::sparql::eval::SimpleEvaluator; @@ -111,10 +111,10 @@ where &mut self, delete: &[QuadPattern], insert: &[QuadPattern], - using: &DatasetSpec, + using: &QueryDataset, algebra: &GraphPattern, ) -> Result<(), EvaluationError> { - let dataset = Rc::new(DatasetView::new(self.read.clone(), false, &[], &[], using)?); + let dataset = Rc::new(DatasetView::new(self.read.clone(), using)?); let (plan, variables) = PlanBuilder::build(dataset.as_ref(), algebra)?; let evaluator = SimpleEvaluator::>::new( dataset.clone(), diff --git a/python/src/memory_store.rs b/python/src/memory_store.rs index 159edb54..1d97c8e7 100644 --- a/python/src/memory_store.rs +++ b/python/src/memory_store.rs @@ -3,6 +3,7 @@ use crate::model::*; use crate::sparql::*; use crate::store_utils::*; use oxigraph::io::{DatasetFormat, GraphFormat}; +use oxigraph::sparql::QueryOptions; use oxigraph::store::memory::*; use pyo3::basic::CompareOp; use pyo3::exceptions::{PyNotImplementedError, PyValueError}; @@ -157,10 +158,15 @@ impl PyMemoryStore { named_graphs: Option<&PyAny>, py: Python<'_>, ) -> PyResult { - let options = build_query_options(use_default_graph_as_union, default_graph, named_graphs)?; + let query = parse_query( + query, + use_default_graph_as_union, + default_graph, + named_graphs, + )?; let results = self .inner - .query(query, options) + .query(query, QueryOptions::default()) .map_err(map_evaluation_error)?; query_results_to_python(py, results) } diff --git a/python/src/sled_store.rs b/python/src/sled_store.rs index b35c2627..a9274db4 100644 --- a/python/src/sled_store.rs +++ b/python/src/sled_store.rs @@ -3,6 +3,7 @@ use crate::model::*; use crate::sparql::*; use crate::store_utils::*; use oxigraph::io::{DatasetFormat, GraphFormat}; +use oxigraph::sparql::QueryOptions; use oxigraph::store::sled::*; use pyo3::exceptions::PyValueError; use pyo3::prelude::{ @@ -171,10 +172,15 @@ impl PySledStore { named_graphs: Option<&PyAny>, py: Python<'_>, ) -> PyResult { - let options = build_query_options(use_default_graph_as_union, default_graph, named_graphs)?; + let query = parse_query( + query, + use_default_graph_as_union, + default_graph, + named_graphs, + )?; let results = self .inner - .query(query, options) + .query(query, QueryOptions::default()) .map_err(map_evaluation_error)?; query_results_to_python(py, results) } diff --git a/python/src/sparql.rs b/python/src/sparql.rs index f3047090..90b6c679 100644 --- a/python/src/sparql.rs +++ b/python/src/sparql.rs @@ -10,47 +10,52 @@ use pyo3::prelude::{ use pyo3::{PyIterProtocol, PyMappingProtocol, PyNativeType, PyObjectProtocol}; use std::vec::IntoIter; -pub fn build_query_options( +pub fn parse_query( + query: &str, use_default_graph_as_union: bool, default_graph: Option<&PyAny>, named_graphs: Option<&PyAny>, -) -> PyResult { - let mut options = QueryOptions::default(); +) -> PyResult { + let mut query = Query::parse(query, None).map_err(|e| map_evaluation_error(e.into()))?; + + if use_default_graph_as_union && default_graph.is_some() { + return Err(PyValueError::new_err( + "The query() method use_default_graph_as_union and default_graph arguments should not be set at the same time", + )); + } + if use_default_graph_as_union { - options = options.with_default_graph_as_union(); + query.dataset_mut().set_default_graph_as_union(); } if let Some(default_graph) = default_graph { if let Ok(default_graphs) = default_graph.iter() { - if default_graph.is_empty()? { - return Err(PyValueError::new_err( - "The query() method default_graph argument cannot be empty list", - )); - } - for default_graph in default_graphs { - options = options.with_default_graph(default_graph?.extract::()?); - } + query.dataset_mut().set_default_graph( + default_graphs + .map(|graph| Ok(graph?.extract::()?.into())) + .collect::>()?, + ) } else if let Ok(default_graph) = default_graph.extract::() { - options = options.with_default_graph(default_graph); + query + .dataset_mut() + .set_default_graph(vec![default_graph.into()]); } else { return Err(PyValueError::new_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_graphs) = named_graphs { - if named_graphs.is_empty()? { - return Err(PyValueError::new_err( - "The query() method named_graphs argument cannot be empty", - )); - } - for named_graph in named_graphs.iter()? { - options = options.with_named_graph(named_graph?.extract::()?); - } + query.dataset_mut().set_available_named_graphs( + named_graphs + .iter()? + .map(|graph| Ok(graph?.extract::()?.into())) + .collect::>()?, + ) } - Ok(options) + Ok(query) } pub fn query_results_to_python(py: Python<'_>, results: QueryResults) -> PyResult { diff --git a/server/src/main.rs b/server/src/main.rs index fe39139f..450d1959 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -17,7 +17,7 @@ use async_std::prelude::*; use async_std::task::{block_on, spawn, spawn_blocking}; use http_types::{headers, Body, Error, Method, Mime, Request, Response, Result, StatusCode}; use oxigraph::io::{DatasetFormat, GraphFormat}; -use oxigraph::model::{GraphName, NamedNode}; +use oxigraph::model::{GraphName, NamedNode, NamedOrBlankNode}; use oxigraph::sparql::{Query, QueryOptions, QueryResults, QueryResultsFormat, Update}; use std::io::BufReader; use std::str::FromStr; @@ -101,9 +101,7 @@ async fn handle_request(request: Request, store: Store) -> Result { { Ok(()) => Response::new(StatusCode::NoContent), Err(error) => { - let mut error = Error::from(error); - error.set_status(StatusCode::BadRequest); - return Err(error); + return Err(bad_request(error)); } } } else { @@ -229,29 +227,26 @@ async fn evaluate_sparql_query( request: Request, ) -> Result { spawn_blocking(move || { - let query = Query::parse(&query, None).map_err(|e| { - let mut e = Error::from(e); - e.set_status(StatusCode::BadRequest); - e - })?; - - let mut options = QueryOptions::default().with_simple_service_handler(); - for default_graph_uri in default_graph_uris { - options = - options.with_default_graph(NamedNode::new(default_graph_uri).map_err(|e| { - let mut e = Error::from(e); - e.set_status(StatusCode::BadRequest); - e - })?) - } - for named_graph_uri in named_graph_uris { - options = options.with_named_graph(NamedNode::new(named_graph_uri).map_err(|e| { - let mut e = Error::from(e); - e.set_status(StatusCode::BadRequest); - e - })?) + let mut query = Query::parse(&query, None).map_err(bad_request)?; + let default_graph_uris = default_graph_uris + .into_iter() + .map(|e| Ok(NamedNode::new(e)?.into())) + .collect::>>() + .map_err(bad_request)?; + let named_graph_uris = named_graph_uris + .into_iter() + .map(|e| Ok(NamedNode::new(e)?.into())) + .collect::>>() + .map_err(bad_request)?; + + if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { + query.dataset_mut().set_default_graph(default_graph_uris); + query + .dataset_mut() + .set_available_named_graphs(named_graph_uris); } + let options = QueryOptions::default().with_simple_service_handler(); let results = store.query(query, options)?; //TODO: stream if let QueryResults::Graph(_) = results { @@ -419,6 +414,12 @@ fn content_negotiation( .ok_or_else(|| Error::from_str(StatusCode::InternalServerError, "Unknown mime type")) } +fn bad_request(e: impl Into) -> Error { + let mut e = e.into(); + e.set_status(StatusCode::BadRequest); + e +} + struct SyncAsyncReader { inner: R, } diff --git a/wikibase/src/main.rs b/wikibase/src/main.rs index 6a805e04..74cb90b2 100644 --- a/wikibase/src/main.rs +++ b/wikibase/src/main.rs @@ -175,14 +175,15 @@ async fn evaluate_sparql_query( ) -> Result { spawn_blocking(move || { //TODO: stream - let query = Query::parse(&query, None).map_err(|e| { + let mut query = Query::parse(&query, None).map_err(|e| { let mut e = Error::from(e); e.set_status(StatusCode::BadRequest); e })?; - let options = QueryOptions::default() - .with_default_graph_as_union() - .with_simple_service_handler(); + if query.dataset().is_default_dataset() { + query.dataset_mut().set_default_graph_as_union(); + } + let options = QueryOptions::default().with_simple_service_handler(); let results = store.query(query, options)?; if let QueryResults::Graph(_) = results { let format = content_negotiation(