Adds custom functions support

pull/175/head
Tpt 3 years ago
parent 553267b63d
commit cfb3be6d34
  1. 23
      lib/src/sparql/eval.rs
  2. 58
      lib/src/sparql/mod.rs
  3. 7
      lib/src/sparql/plan.rs
  4. 31
      lib/src/sparql/plan_builder.rs
  5. 8
      lib/src/sparql/update.rs

@ -1,7 +1,7 @@
use crate::model::vocab::{rdf, xsd}; use crate::model::vocab::{rdf, xsd};
use crate::model::xsd::*; use crate::model::xsd::*;
use crate::model::Triple;
use crate::model::{BlankNode, LiteralRef, NamedNodeRef}; use crate::model::{BlankNode, LiteralRef, NamedNodeRef};
use crate::model::{NamedNode, Term, Triple};
use crate::sparql::algebra::{Query, QueryDataset}; use crate::sparql::algebra::{Query, QueryDataset};
use crate::sparql::dataset::DatasetView; use crate::sparql::dataset::DatasetView;
use crate::sparql::error::EvaluationError; use crate::sparql::error::EvaluationError;
@ -38,6 +38,7 @@ pub struct SimpleEvaluator {
base_iri: Option<Rc<Iri<String>>>, base_iri: Option<Rc<Iri<String>>>,
now: DateTime, now: DateTime,
service_handler: Rc<dyn ServiceHandler<Error = EvaluationError>>, service_handler: Rc<dyn ServiceHandler<Error = EvaluationError>>,
custom_functions: Rc<HashMap<NamedNode, Rc<dyn Fn(&[Term]) -> Option<Term>>>>,
} }
impl SimpleEvaluator { impl SimpleEvaluator {
@ -45,12 +46,14 @@ impl SimpleEvaluator {
dataset: Rc<DatasetView>, dataset: Rc<DatasetView>,
base_iri: Option<Rc<Iri<String>>>, base_iri: Option<Rc<Iri<String>>>,
service_handler: Rc<dyn ServiceHandler<Error = EvaluationError>>, service_handler: Rc<dyn ServiceHandler<Error = EvaluationError>>,
custom_functions: Rc<HashMap<NamedNode, Rc<dyn Fn(&[Term]) -> Option<Term>>>>,
) -> Self { ) -> Self {
Self { Self {
dataset, dataset,
base_iri, base_iri,
now: DateTime::now().unwrap(), now: DateTime::now().unwrap(),
service_handler, service_handler,
custom_functions,
} }
} }
@ -2016,6 +2019,24 @@ impl SimpleEvaluator {
_ => None, _ => None,
}) })
} }
PlanExpression::CustomFunction(function_name, args) => {
if let Some(function) = self.custom_functions.get(function_name).cloned() {
let args = args
.iter()
.map(|e| self.expression_evaluator(e))
.collect::<Vec<_>>();
let dataset = self.dataset.clone();
Rc::new(move |tuple| {
let args = args
.iter()
.map(|f| dataset.decode_term(&f(tuple)?).ok())
.collect::<Option<Vec<_>>>()?;
Some(dataset.encode_term(&function(&args)?))
})
} else {
Rc::new(|_| None)
}
}
} }
} }

@ -16,6 +16,7 @@ mod service;
mod update; mod update;
mod xml_results; mod xml_results;
use crate::model::{NamedNode, Term};
pub use crate::sparql::algebra::{Query, Update}; pub use crate::sparql::algebra::{Query, Update};
use crate::sparql::dataset::DatasetView; use crate::sparql::dataset::DatasetView;
pub use crate::sparql::error::EvaluationError; pub use crate::sparql::error::EvaluationError;
@ -32,6 +33,7 @@ use crate::sparql::service::{EmptyServiceHandler, ErrorConversionServiceHandler}
pub(crate) use crate::sparql::update::evaluate_update; pub(crate) use crate::sparql::update::evaluate_update;
use crate::storage::Storage; use crate::storage::Storage;
pub use spargebra::ParseError; pub use spargebra::ParseError;
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
@ -47,11 +49,13 @@ pub(crate) fn evaluate_query(
spargebra::Query::Select { spargebra::Query::Select {
pattern, base_iri, .. pattern, base_iri, ..
} => { } => {
let (plan, variables) = PlanBuilder::build(&dataset, &pattern, true)?; let (plan, variables) =
PlanBuilder::build(&dataset, &pattern, true, &options.custom_functions)?;
Ok(SimpleEvaluator::new( Ok(SimpleEvaluator::new(
Rc::new(dataset), Rc::new(dataset),
base_iri.map(Rc::new), base_iri.map(Rc::new),
options.service_handler(), options.service_handler(),
Rc::new(options.custom_functions),
) )
.evaluate_select_plan( .evaluate_select_plan(
&plan, &plan,
@ -66,11 +70,13 @@ pub(crate) fn evaluate_query(
spargebra::Query::Ask { spargebra::Query::Ask {
pattern, base_iri, .. pattern, base_iri, ..
} => { } => {
let (plan, _) = PlanBuilder::build(&dataset, &pattern, false)?; let (plan, _) =
PlanBuilder::build(&dataset, &pattern, false, &options.custom_functions)?;
SimpleEvaluator::new( SimpleEvaluator::new(
Rc::new(dataset), Rc::new(dataset),
base_iri.map(Rc::new), base_iri.map(Rc::new),
options.service_handler(), options.service_handler(),
Rc::new(options.custom_functions),
) )
.evaluate_ask_plan(&plan) .evaluate_ask_plan(&plan)
} }
@ -80,23 +86,32 @@ pub(crate) fn evaluate_query(
base_iri, base_iri,
.. ..
} => { } => {
let (plan, variables) = PlanBuilder::build(&dataset, &pattern, false)?; let (plan, variables) =
let construct = PlanBuilder::build_graph_template(&dataset, &template, variables); PlanBuilder::build(&dataset, &pattern, false, &options.custom_functions)?;
let construct = PlanBuilder::build_graph_template(
&dataset,
&template,
variables,
&options.custom_functions,
);
Ok(SimpleEvaluator::new( Ok(SimpleEvaluator::new(
Rc::new(dataset), Rc::new(dataset),
base_iri.map(Rc::new), base_iri.map(Rc::new),
options.service_handler(), options.service_handler(),
Rc::new(options.custom_functions),
) )
.evaluate_construct_plan(&plan, construct)) .evaluate_construct_plan(&plan, construct))
} }
spargebra::Query::Describe { spargebra::Query::Describe {
pattern, base_iri, .. pattern, base_iri, ..
} => { } => {
let (plan, _) = PlanBuilder::build(&dataset, &pattern, false)?; let (plan, _) =
PlanBuilder::build(&dataset, &pattern, false, &options.custom_functions)?;
Ok(SimpleEvaluator::new( Ok(SimpleEvaluator::new(
Rc::new(dataset), Rc::new(dataset),
base_iri.map(Rc::new), base_iri.map(Rc::new),
options.service_handler(), options.service_handler(),
Rc::new(options.custom_functions),
) )
.evaluate_describe_plan(&plan)) .evaluate_describe_plan(&plan))
} }
@ -110,7 +125,8 @@ pub(crate) fn evaluate_query(
/// a simple HTTP 1.1 client is used to execute [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE calls. /// a simple HTTP 1.1 client is used to execute [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE calls.
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct QueryOptions { pub struct QueryOptions {
pub(crate) service_handler: Option<Rc<dyn ServiceHandler<Error = EvaluationError>>>, service_handler: Option<Rc<dyn ServiceHandler<Error = EvaluationError>>>,
custom_functions: HashMap<NamedNode, Rc<dyn Fn(&[Term]) -> Option<Term>>>,
http_timeout: Option<Duration>, http_timeout: Option<Duration>,
} }
@ -138,6 +154,36 @@ impl QueryOptions {
self self
} }
/// Adds a custom SPARQL evaluation function
///
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
/// use oxigraph::sparql::{QueryOptions, QueryResults};
///
/// let store = Store::new()?;
///
/// if let QueryResults::Solutions(mut solutions) = store.query_opt(
/// "SELECT (<http://www.w3.org/ns/formats/N-Triples>(1) AS ?nt) WHERE {}",
/// QueryOptions::default().with_custom_function(
/// NamedNode::new("http://www.w3.org/ns/formats/N-Triples")?,
/// |args| args.get(0).map(|t| Literal::from(t.to_string()).into())
/// )
/// )? {
/// assert_eq!(solutions.next().unwrap()?.get("nt"), Some(&Literal::from("\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>").into()));
/// }
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
#[inline]
pub fn with_custom_function(
mut self,
name: NamedNode,
evaluator: impl Fn(&[Term]) -> Option<Term> + 'static,
) -> Self {
self.custom_functions.insert(name, Rc::new(evaluator));
self
}
fn service_handler(&self) -> Rc<dyn ServiceHandler<Error = EvaluationError>> { fn service_handler(&self) -> Rc<dyn ServiceHandler<Error = EvaluationError>> {
self.service_handler.clone().unwrap_or_else(|| { self.service_handler.clone().unwrap_or_else(|| {
if cfg!(feature = "http_client") { if cfg!(feature = "http_client") {

@ -1,3 +1,4 @@
use crate::model::NamedNode;
use crate::sparql::model::Variable; use crate::sparql::model::Variable;
use crate::storage::numeric_encoder::EncodedTerm; use crate::storage::numeric_encoder::EncodedTerm;
use spargebra::algebra::GraphPattern; use spargebra::algebra::GraphPattern;
@ -454,6 +455,7 @@ pub enum PlanExpression {
YearMonthDurationCast(Box<Self>), YearMonthDurationCast(Box<Self>),
DayTimeDurationCast(Box<Self>), DayTimeDurationCast(Box<Self>),
StringCast(Box<Self>), StringCast(Box<Self>),
CustomFunction(NamedNode, Vec<Self>),
} }
impl PlanExpression { impl PlanExpression {
@ -557,8 +559,9 @@ impl PlanExpression {
c.lookup_used_variables(callback); c.lookup_used_variables(callback);
d.lookup_used_variables(callback); d.lookup_used_variables(callback);
} }
PlanExpression::Concat(es)
PlanExpression::Concat(es) | PlanExpression::Coalesce(es) => { | PlanExpression::Coalesce(es)
| PlanExpression::CustomFunction(_, es) => {
for e in es { for e in es {
e.lookup_used_variables(callback); e.lookup_used_variables(callback);
} }

@ -1,4 +1,5 @@
use crate::model::{LiteralRef, NamedNodeRef}; use crate::error::invalid_data_error;
use crate::model::{LiteralRef, NamedNode as OxNamedNode, NamedNodeRef, Term as OxTerm};
use crate::sparql::dataset::DatasetView; use crate::sparql::dataset::DatasetView;
use crate::sparql::error::EvaluationError; use crate::sparql::error::EvaluationError;
use crate::sparql::model::Variable as OxVariable; use crate::sparql::model::Variable as OxVariable;
@ -7,12 +8,13 @@ use crate::storage::numeric_encoder::{EncodedTerm, EncodedTriple};
use rand::random; use rand::random;
use spargebra::algebra::*; use spargebra::algebra::*;
use spargebra::term::*; use spargebra::term::*;
use std::collections::{BTreeSet, HashSet}; use std::collections::{BTreeSet, HashMap, HashSet};
use std::mem::swap; use std::mem::swap;
use std::rc::Rc; use std::rc::Rc;
pub struct PlanBuilder<'a> { pub struct PlanBuilder<'a> {
dataset: &'a DatasetView, dataset: &'a DatasetView,
custom_functions: &'a HashMap<OxNamedNode, Rc<dyn Fn(&[OxTerm]) -> Option<OxTerm>>>,
} }
impl<'a> PlanBuilder<'a> { impl<'a> PlanBuilder<'a> {
@ -20,9 +22,14 @@ impl<'a> PlanBuilder<'a> {
dataset: &'a DatasetView, dataset: &'a DatasetView,
pattern: &GraphPattern, pattern: &GraphPattern,
is_cardinality_meaningful: bool, is_cardinality_meaningful: bool,
custom_functions: &'a HashMap<OxNamedNode, Rc<dyn Fn(&[OxTerm]) -> Option<OxTerm>>>,
) -> Result<(PlanNode, Vec<Variable>), EvaluationError> { ) -> Result<(PlanNode, Vec<Variable>), EvaluationError> {
let mut variables = Vec::default(); let mut variables = Vec::default();
let plan = PlanBuilder { dataset }.build_for_graph_pattern( let plan = PlanBuilder {
dataset,
custom_functions,
}
.build_for_graph_pattern(
pattern, pattern,
&mut variables, &mut variables,
&PatternValue::Constant(EncodedTerm::DefaultGraph), &PatternValue::Constant(EncodedTerm::DefaultGraph),
@ -43,8 +50,13 @@ impl<'a> PlanBuilder<'a> {
dataset: &'a DatasetView, dataset: &'a DatasetView,
template: &[TriplePattern], template: &[TriplePattern],
mut variables: Vec<Variable>, mut variables: Vec<Variable>,
custom_functions: &'a HashMap<OxNamedNode, Rc<dyn Fn(&[OxTerm]) -> Option<OxTerm>>>,
) -> Vec<TripleTemplate> { ) -> Vec<TripleTemplate> {
PlanBuilder { dataset }.build_for_graph_template(template, &mut variables) PlanBuilder {
dataset,
custom_functions,
}
.build_for_graph_template(template, &mut variables)
} }
fn build_for_graph_pattern( fn build_for_graph_pattern(
@ -628,7 +640,16 @@ impl<'a> PlanBuilder<'a> {
self.build_for_expression(&parameters[0], variables, graph_name)?, self.build_for_expression(&parameters[0], variables, graph_name)?,
)), )),
Function::Custom(name) => { Function::Custom(name) => {
if name.iri == "http://www.w3.org/2001/XMLSchema#boolean" { let ox_name = OxNamedNode::new(&name.iri).map_err(invalid_data_error)?;
if self.custom_functions.contains_key(&ox_name) {
PlanExpression::CustomFunction(
ox_name,
parameters
.iter()
.map(|p| self.build_for_expression(p, variables, graph_name))
.collect::<Result<Vec<_>, EvaluationError>>()?,
)
} else if name.iri == "http://www.w3.org/2001/XMLSchema#boolean" {
self.build_cast( self.build_cast(
parameters, parameters,
PlanExpression::BooleanCast, PlanExpression::BooleanCast,

@ -138,11 +138,17 @@ impl SimpleUpdateEvaluator<'_> {
algebra: &GraphPattern, algebra: &GraphPattern,
) -> Result<(), EvaluationError> { ) -> Result<(), EvaluationError> {
let dataset = Rc::new(DatasetView::new(self.transaction.reader(), using)); let dataset = Rc::new(DatasetView::new(self.transaction.reader(), using));
let (plan, variables) = PlanBuilder::build(dataset.as_ref(), algebra, false)?; let (plan, variables) = PlanBuilder::build(
dataset.as_ref(),
algebra,
false,
&self.options.query_options.custom_functions,
)?;
let evaluator = SimpleEvaluator::new( let evaluator = SimpleEvaluator::new(
dataset.clone(), dataset.clone(),
self.base_iri.clone(), self.base_iri.clone(),
self.options.query_options.service_handler(), self.options.query_options.service_handler(),
Rc::new(self.options.query_options.custom_functions.clone()),
); );
let mut bnodes = HashMap::new(); let mut bnodes = HashMap::new();
for tuple in evaluator.plan_evaluator(&plan)(EncodedTuple::with_capacity(variables.len())) { for tuple in evaluator.plan_evaluator(&plan)(EncodedTuple::with_capacity(variables.len())) {

Loading…
Cancel
Save