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::xsd::*;
use crate::model::Triple;
use crate::model::{BlankNode, LiteralRef, NamedNodeRef};
use crate::model::{NamedNode, Term, Triple};
use crate::sparql::algebra::{Query, QueryDataset};
use crate::sparql::dataset::DatasetView;
use crate::sparql::error::EvaluationError;
@ -38,6 +38,7 @@ pub struct SimpleEvaluator {
base_iri: Option<Rc<Iri<String>>>,
now: DateTime,
service_handler: Rc<dyn ServiceHandler<Error = EvaluationError>>,
custom_functions: Rc<HashMap<NamedNode, Rc<dyn Fn(&[Term]) -> Option<Term>>>>,
}
impl SimpleEvaluator {
@ -45,12 +46,14 @@ impl SimpleEvaluator {
dataset: Rc<DatasetView>,
base_iri: Option<Rc<Iri<String>>>,
service_handler: Rc<dyn ServiceHandler<Error = EvaluationError>>,
custom_functions: Rc<HashMap<NamedNode, Rc<dyn Fn(&[Term]) -> Option<Term>>>>,
) -> Self {
Self {
dataset,
base_iri,
now: DateTime::now().unwrap(),
service_handler,
custom_functions,
}
}
@ -2016,6 +2019,24 @@ impl SimpleEvaluator {
_ => 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 xml_results;
use crate::model::{NamedNode, Term};
pub use crate::sparql::algebra::{Query, Update};
use crate::sparql::dataset::DatasetView;
pub use crate::sparql::error::EvaluationError;
@ -32,6 +33,7 @@ use crate::sparql::service::{EmptyServiceHandler, ErrorConversionServiceHandler}
pub(crate) use crate::sparql::update::evaluate_update;
use crate::storage::Storage;
pub use spargebra::ParseError;
use std::collections::HashMap;
use std::rc::Rc;
use std::time::Duration;
@ -47,11 +49,13 @@ pub(crate) fn evaluate_query(
spargebra::Query::Select {
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(
Rc::new(dataset),
base_iri.map(Rc::new),
options.service_handler(),
Rc::new(options.custom_functions),
)
.evaluate_select_plan(
&plan,
@ -66,11 +70,13 @@ pub(crate) fn evaluate_query(
spargebra::Query::Ask {
pattern, base_iri, ..
} => {
let (plan, _) = PlanBuilder::build(&dataset, &pattern, false)?;
let (plan, _) =
PlanBuilder::build(&dataset, &pattern, false, &options.custom_functions)?;
SimpleEvaluator::new(
Rc::new(dataset),
base_iri.map(Rc::new),
options.service_handler(),
Rc::new(options.custom_functions),
)
.evaluate_ask_plan(&plan)
}
@ -80,23 +86,32 @@ pub(crate) fn evaluate_query(
base_iri,
..
} => {
let (plan, variables) = PlanBuilder::build(&dataset, &pattern, false)?;
let construct = PlanBuilder::build_graph_template(&dataset, &template, variables);
let (plan, variables) =
PlanBuilder::build(&dataset, &pattern, false, &options.custom_functions)?;
let construct = PlanBuilder::build_graph_template(
&dataset,
&template,
variables,
&options.custom_functions,
);
Ok(SimpleEvaluator::new(
Rc::new(dataset),
base_iri.map(Rc::new),
options.service_handler(),
Rc::new(options.custom_functions),
)
.evaluate_construct_plan(&plan, construct))
}
spargebra::Query::Describe {
pattern, base_iri, ..
} => {
let (plan, _) = PlanBuilder::build(&dataset, &pattern, false)?;
let (plan, _) =
PlanBuilder::build(&dataset, &pattern, false, &options.custom_functions)?;
Ok(SimpleEvaluator::new(
Rc::new(dataset),
base_iri.map(Rc::new),
options.service_handler(),
Rc::new(options.custom_functions),
)
.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.
#[derive(Clone, Default)]
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>,
}
@ -138,6 +154,36 @@ impl QueryOptions {
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>> {
self.service_handler.clone().unwrap_or_else(|| {
if cfg!(feature = "http_client") {

@ -1,3 +1,4 @@
use crate::model::NamedNode;
use crate::sparql::model::Variable;
use crate::storage::numeric_encoder::EncodedTerm;
use spargebra::algebra::GraphPattern;
@ -454,6 +455,7 @@ pub enum PlanExpression {
YearMonthDurationCast(Box<Self>),
DayTimeDurationCast(Box<Self>),
StringCast(Box<Self>),
CustomFunction(NamedNode, Vec<Self>),
}
impl PlanExpression {
@ -557,8 +559,9 @@ impl PlanExpression {
c.lookup_used_variables(callback);
d.lookup_used_variables(callback);
}
PlanExpression::Concat(es) | PlanExpression::Coalesce(es) => {
PlanExpression::Concat(es)
| PlanExpression::Coalesce(es)
| PlanExpression::CustomFunction(_, es) => {
for e in es {
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::error::EvaluationError;
use crate::sparql::model::Variable as OxVariable;
@ -7,12 +8,13 @@ use crate::storage::numeric_encoder::{EncodedTerm, EncodedTriple};
use rand::random;
use spargebra::algebra::*;
use spargebra::term::*;
use std::collections::{BTreeSet, HashSet};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::mem::swap;
use std::rc::Rc;
pub struct PlanBuilder<'a> {
dataset: &'a DatasetView,
custom_functions: &'a HashMap<OxNamedNode, Rc<dyn Fn(&[OxTerm]) -> Option<OxTerm>>>,
}
impl<'a> PlanBuilder<'a> {
@ -20,9 +22,14 @@ impl<'a> PlanBuilder<'a> {
dataset: &'a DatasetView,
pattern: &GraphPattern,
is_cardinality_meaningful: bool,
custom_functions: &'a HashMap<OxNamedNode, Rc<dyn Fn(&[OxTerm]) -> Option<OxTerm>>>,
) -> Result<(PlanNode, Vec<Variable>), EvaluationError> {
let mut variables = Vec::default();
let plan = PlanBuilder { dataset }.build_for_graph_pattern(
let plan = PlanBuilder {
dataset,
custom_functions,
}
.build_for_graph_pattern(
pattern,
&mut variables,
&PatternValue::Constant(EncodedTerm::DefaultGraph),
@ -43,8 +50,13 @@ impl<'a> PlanBuilder<'a> {
dataset: &'a DatasetView,
template: &[TriplePattern],
mut variables: Vec<Variable>,
custom_functions: &'a HashMap<OxNamedNode, Rc<dyn Fn(&[OxTerm]) -> Option<OxTerm>>>,
) -> 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(
@ -628,7 +640,16 @@ impl<'a> PlanBuilder<'a> {
self.build_for_expression(&parameters[0], variables, graph_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(
parameters,
PlanExpression::BooleanCast,

@ -138,11 +138,17 @@ impl SimpleUpdateEvaluator<'_> {
algebra: &GraphPattern,
) -> Result<(), EvaluationError> {
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(
dataset.clone(),
self.base_iri.clone(),
self.options.query_options.service_handler(),
Rc::new(self.options.query_options.custom_functions.clone()),
);
let mut bnodes = HashMap::new();
for tuple in evaluator.plan_evaluator(&plan)(EncodedTuple::with_capacity(variables.len())) {

Loading…
Cancel
Save