use crate::extensions::{
AggregateState, CustomAggregate, CustomFunction, ExecutionContext, ExtensionRegistry, Value,
ValueType,
};
use anyhow::{bail, Result};
use chrono::Datelike;
use regex::Regex;
pub fn register_builtin_functions(registry: &ExtensionRegistry) -> Result<()> {
registry.register_function(StrFunction)?;
registry.register_function(LangFunction)?;
registry.register_function(DatatypeFunction)?;
registry.register_function(BoundFunction)?;
registry.register_function(IriFunction)?;
registry.register_function(UriFunction)?;
registry.register_function(BlankFunction)?;
registry.register_function(LiteralFunction)?;
registry.register_function(StrlenFunction)?;
registry.register_function(SubstrFunction)?;
registry.register_function(UcaseFunction)?;
registry.register_function(LcaseFunction)?;
registry.register_function(StrstartsFunction)?;
registry.register_function(StrendsFunction)?;
registry.register_function(ContainsFunction)?;
registry.register_function(ConcatFunction)?;
registry.register_function(EncodeForUriFunction)?;
registry.register_function(ReplaceFunction)?;
registry.register_function(AbsFunction)?;
registry.register_function(RoundFunction)?;
registry.register_function(CeilFunction)?;
registry.register_function(FloorFunction)?;
registry.register_function(RandFunction)?;
registry.register_function(NowFunction)?;
registry.register_function(YearFunction)?;
registry.register_function(MonthFunction)?;
registry.register_function(DayFunction)?;
registry.register_function(HoursFunction)?;
registry.register_function(MinutesFunction)?;
registry.register_function(SecondsFunction)?;
registry.register_function(TimezoneFunction)?;
registry.register_function(TzFunction)?;
registry.register_function(Md5Function)?;
registry.register_function(Sha1Function)?;
registry.register_function(Sha256Function)?;
registry.register_function(Sha384Function)?;
registry.register_function(Sha512Function)?;
registry.register_function(IsIriFunction)?;
registry.register_function(IsUriFunction)?;
registry.register_function(IsBlankFunction)?;
registry.register_function(IsLiteralFunction)?;
registry.register_function(IsNumericFunction)?;
registry.register_function(IfFunction)?;
registry.register_function(CoalesceFunction)?;
registry.register_function(RegexFunction)?;
registry.register_function(crate::triple_functions::TripleFunction)?;
registry.register_function(crate::triple_functions::SubjectFunction)?;
registry.register_function(crate::triple_functions::PredicateFunction)?;
registry.register_function(crate::triple_functions::ObjectFunction)?;
registry.register_function(crate::triple_functions::IsTripleFunction)?;
registry.register_function(crate::string_functions_ext::StrBeforeFunction)?;
registry.register_function(crate::string_functions_ext::StrAfterFunction)?;
registry.register_function(crate::string_functions_ext::StrLangFunction)?;
registry.register_function(crate::string_functions_ext::StrLangDirFunction)?;
registry.register_function(crate::string_functions_ext::StrDtFunction)?;
registry.register_aggregate(CountAggregate)?;
registry.register_aggregate(SumAggregate)?;
registry.register_aggregate(AvgAggregate)?;
registry.register_aggregate(MinAggregate)?;
registry.register_aggregate(MaxAggregate)?;
registry.register_aggregate(GroupConcatAggregate)?;
registry.register_aggregate(SampleAggregate)?;
Ok(())
}
#[derive(Debug, Clone)]
struct StrFunction;
impl CustomFunction for StrFunction {
fn name(&self) -> &str {
"http://www.w3.org/2001/XMLSchema#string"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Literal]
}
fn return_type(&self) -> ValueType {
ValueType::String
}
fn documentation(&self) -> &str {
"Returns the string value of a literal"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("str() requires exactly 1 argument");
}
match &args[0] {
Value::String(s) => Ok(Value::String(s.clone())),
Value::Literal { value, .. } => Ok(Value::String(value.clone())),
Value::Iri(iri) => Ok(Value::String(iri.clone())),
Value::Integer(i) => Ok(Value::String(i.to_string())),
Value::Float(f) => Ok(Value::String(f.to_string())),
Value::Boolean(b) => Ok(Value::String(b.to_string())),
_ => bail!("Cannot convert {} to string", args[0].type_name()),
}
}
}
#[derive(Debug, Clone)]
struct LangFunction;
impl CustomFunction for LangFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#lang"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Literal]
}
fn return_type(&self) -> ValueType {
ValueType::String
}
fn documentation(&self) -> &str {
"Returns the language tag of a literal"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("lang() requires exactly 1 argument");
}
match &args[0] {
Value::Literal {
language: Some(lang),
..
} => Ok(Value::String(lang.clone())),
Value::Literal { language: None, .. } => Ok(Value::String("".to_string())),
_ => bail!("lang() can only be applied to literals"),
}
}
}
#[derive(Debug, Clone)]
struct DatatypeFunction;
impl CustomFunction for DatatypeFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#datatype"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Literal]
}
fn return_type(&self) -> ValueType {
ValueType::Iri
}
fn documentation(&self) -> &str {
"Returns the datatype IRI of a literal"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("datatype() requires exactly 1 argument");
}
match &args[0] {
Value::Literal {
datatype: Some(dt),
language: None,
..
} => Ok(Value::Iri(dt.clone())),
Value::Literal {
datatype: None,
language: Some(_),
..
} => Ok(Value::Iri(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString".to_string(),
)),
Value::Literal {
datatype: None,
language: None,
..
} => Ok(Value::Iri(
"http://www.w3.org/2001/XMLSchema#string".to_string(),
)),
_ => bail!("datatype() can only be applied to literals"),
}
}
}
#[derive(Debug, Clone)]
struct BoundFunction;
impl CustomFunction for BoundFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/sparql-results#bound"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::Boolean
}
fn documentation(&self) -> &str {
"Tests whether a variable is bound"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("bound() requires exactly 1 argument");
}
match &args[0] {
Value::String(var_name) => {
if let Ok(variable) = oxirs_core::Variable::new(var_name) {
let is_bound = context.variables.contains_key(&variable);
Ok(Value::Boolean(is_bound))
} else {
Ok(Value::Boolean(false))
}
}
_ => bail!("bound() requires a variable name as string"),
}
}
}
#[derive(Debug, Clone)]
struct IriFunction;
impl CustomFunction for IriFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#iri"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::Iri
}
fn documentation(&self) -> &str {
"Constructs an IRI from a string"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("iri() requires exactly 1 argument");
}
match &args[0] {
Value::String(s) => {
let resolved = if let Some(base) = &context.base_iri {
if s.starts_with("http://")
|| s.starts_with("https://")
|| s.starts_with("ftp://")
{
s.clone()
} else {
format!("{base}{s}")
}
} else {
s.clone()
};
Ok(Value::Iri(resolved))
}
Value::Iri(iri) => Ok(Value::Iri(iri.clone())),
_ => bail!("iri() requires a string argument"),
}
}
}
#[derive(Debug, Clone)]
struct UriFunction;
impl CustomFunction for UriFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#uri"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::Iri
}
fn documentation(&self) -> &str {
"Alias for iri() function"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], context: &ExecutionContext) -> Result<Value> {
IriFunction.execute(args, context)
}
}
#[derive(Debug, Clone)]
struct BlankFunction;
impl CustomFunction for BlankFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#bnode"
}
fn arity(&self) -> Option<usize> {
None
} fn parameter_types(&self) -> Vec<ValueType> {
vec![]
}
fn return_type(&self) -> ValueType {
ValueType::BlankNode
}
fn documentation(&self) -> &str {
"Constructs a blank node"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
match args.len() {
0 => {
let id = format!(
"_:gen{r}",
r = {
use scirs2_core::random::{rng, RngExt};
let mut random = rng();
random.random::<u32>()
}
);
Ok(Value::BlankNode(id))
}
1 => match &args[0] {
Value::String(s) => Ok(Value::BlankNode(format!("_{s}"))),
_ => bail!("bnode() requires a string argument"),
},
_ => bail!("bnode() takes 0 or 1 arguments"),
}
}
}
#[derive(Debug, Clone)]
struct LiteralFunction;
impl CustomFunction for LiteralFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#literal"
}
fn arity(&self) -> Option<usize> {
None
} fn parameter_types(&self) -> Vec<ValueType> {
vec![]
}
fn return_type(&self) -> ValueType {
ValueType::Literal
}
fn documentation(&self) -> &str {
"Constructs a literal with optional language or datatype"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
match args.len() {
1 => match &args[0] {
Value::String(s) => Ok(Value::Literal {
value: s.clone(),
language: None,
datatype: None,
}),
_ => bail!("literal() first argument must be a string"),
},
2 => {
match (&args[0], &args[1]) {
(Value::String(s), Value::String(lang_or_dt)) => {
if lang_or_dt.starts_with("http://") {
Ok(Value::Literal {
value: s.clone(),
language: None,
datatype: Some(lang_or_dt.clone()),
})
} else {
Ok(Value::Literal {
value: s.clone(),
language: Some(lang_or_dt.clone()),
datatype: None,
})
}
}
_ => bail!("literal() arguments must be strings"),
}
}
_ => bail!("literal() takes 1 or 2 arguments"),
}
}
}
#[derive(Debug, Clone)]
struct StrlenFunction;
impl CustomFunction for StrlenFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#string-length"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::Integer
}
fn documentation(&self) -> &str {
"Returns the length of a string"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("strlen() requires exactly 1 argument");
}
let s = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("strlen() requires a string argument"),
};
Ok(Value::Integer(s.chars().count() as i64))
}
}
#[derive(Debug, Clone)]
struct SubstrFunction;
impl CustomFunction for SubstrFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#substring"
}
fn arity(&self) -> Option<usize> {
None
} fn parameter_types(&self) -> Vec<ValueType> {
vec![]
}
fn return_type(&self) -> ValueType {
ValueType::String
}
fn documentation(&self) -> &str {
"Returns a substring of a string"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 2 && args.len() != 3 {
bail!("substr() requires 2 or 3 arguments");
}
let s = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("substr() first argument must be a string"),
};
let start = match &args[1] {
Value::Integer(i) => *i as usize,
_ => bail!("substr() second argument must be an integer"),
};
let chars: Vec<char> = s.chars().collect();
if start > chars.len() {
return Ok(Value::String("".to_string()));
}
let result = if args.len() == 3 {
let length = match &args[2] {
Value::Integer(i) => *i as usize,
_ => bail!("substr() third argument must be an integer"),
};
let end = std::cmp::min(start + length, chars.len());
chars[start..end].iter().collect()
} else {
chars[start..].iter().collect()
};
Ok(Value::String(result))
}
}
#[derive(Debug, Clone)]
struct UcaseFunction;
impl CustomFunction for UcaseFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#upper-case"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::String
}
fn documentation(&self) -> &str {
"Converts a string to uppercase"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("ucase() requires exactly 1 argument");
}
let s = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("ucase() requires a string argument"),
};
Ok(Value::String(s.to_uppercase()))
}
}
#[derive(Debug, Clone)]
struct LcaseFunction;
impl CustomFunction for LcaseFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#lower-case"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::String
}
fn documentation(&self) -> &str {
"Converts a string to lowercase"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("lcase() requires exactly 1 argument");
}
let s = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("lcase() requires a string argument"),
};
Ok(Value::String(s.to_lowercase()))
}
}
#[derive(Debug, Clone)]
struct StrstartsFunction;
impl CustomFunction for StrstartsFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#starts-with"
}
fn arity(&self) -> Option<usize> {
Some(2)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String, ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::Boolean
}
fn documentation(&self) -> &str {
"Tests if a string starts with another string"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 2 {
bail!("strstarts() requires exactly 2 arguments");
}
let s1 = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("strstarts() first argument must be a string"),
};
let s2 = match &args[1] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("strstarts() second argument must be a string"),
};
Ok(Value::Boolean(s1.starts_with(s2)))
}
}
#[derive(Debug, Clone)]
struct StrendsFunction;
impl CustomFunction for StrendsFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#ends-with"
}
fn arity(&self) -> Option<usize> {
Some(2)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String, ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::Boolean
}
fn documentation(&self) -> &str {
"Tests if a string ends with another string"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 2 {
bail!("strends() requires exactly 2 arguments");
}
let s1 = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("strends() first argument must be a string"),
};
let s2 = match &args[1] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("strends() second argument must be a string"),
};
Ok(Value::Boolean(s1.ends_with(s2)))
}
}
#[derive(Debug, Clone)]
struct ContainsFunction;
impl CustomFunction for ContainsFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#contains"
}
fn arity(&self) -> Option<usize> {
Some(2)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String, ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::Boolean
}
fn documentation(&self) -> &str {
"Tests if a string contains another string"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 2 {
bail!("contains() requires exactly 2 arguments");
}
let s1 = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("contains() first argument must be a string"),
};
let s2 = match &args[1] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("contains() second argument must be a string"),
};
Ok(Value::Boolean(s1.contains(s2)))
}
}
#[derive(Debug, Clone)]
struct ConcatFunction;
impl CustomFunction for ConcatFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#concat"
}
fn arity(&self) -> Option<usize> {
None
} fn parameter_types(&self) -> Vec<ValueType> {
vec![]
}
fn return_type(&self) -> ValueType {
ValueType::String
}
fn documentation(&self) -> &str {
"Concatenates multiple strings"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.is_empty() {
return Ok(Value::String("".to_string()));
}
let mut result = String::new();
for arg in args {
let s = match arg {
Value::String(s) => s,
Value::Literal { value, .. } => value,
Value::Integer(i) => &i.to_string(),
Value::Float(f) => &f.to_string(),
Value::Boolean(b) => &b.to_string(),
_ => bail!("concat() arguments must be convertible to strings"),
};
result.push_str(s);
}
Ok(Value::String(result))
}
}
#[derive(Debug, Clone)]
struct EncodeForUriFunction;
impl CustomFunction for EncodeForUriFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#encode-for-uri"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::String
}
fn documentation(&self) -> &str {
"Encodes a string for use in a URI"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("encode_for_uri() requires exactly 1 argument");
}
let s = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("encode_for_uri() requires a string argument"),
};
let encoded = s
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || "-_.~".contains(c) {
c.to_string()
} else {
format!("%{c:02X}", c = c as u8)
}
})
.collect();
Ok(Value::String(encoded))
}
}
#[derive(Debug, Clone)]
struct ReplaceFunction;
impl CustomFunction for ReplaceFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#replace"
}
fn arity(&self) -> Option<usize> {
None
} fn parameter_types(&self) -> Vec<ValueType> {
vec![]
}
fn return_type(&self) -> ValueType {
ValueType::String
}
fn documentation(&self) -> &str {
"Replaces occurrences of a pattern in a string"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 3 && args.len() != 4 {
bail!("replace() requires 3 or 4 arguments");
}
let input = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("replace() first argument must be a string"),
};
let pattern = match &args[1] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("replace() second argument must be a string"),
};
let replacement = match &args[2] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("replace() third argument must be a string"),
};
let result = input.replace(pattern, replacement);
Ok(Value::String(result))
}
}
#[derive(Debug, Clone)]
struct AbsFunction;
impl CustomFunction for AbsFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#abs"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Float]
}
fn return_type(&self) -> ValueType {
ValueType::Float
}
fn documentation(&self) -> &str {
"Returns the absolute value of a number"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("abs() requires exactly 1 argument");
}
match &args[0] {
Value::Integer(i) => Ok(Value::Integer(i.abs())),
Value::Float(f) => Ok(Value::Float(f.abs())),
_ => bail!("abs() requires a numeric argument"),
}
}
}
#[derive(Debug, Clone)]
struct RoundFunction;
impl CustomFunction for RoundFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#round"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Float]
}
fn return_type(&self) -> ValueType {
ValueType::Float
}
fn documentation(&self) -> &str {
"Rounds a number to the nearest integer"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("round() requires exactly 1 argument");
}
match &args[0] {
Value::Integer(i) => Ok(Value::Integer(*i)),
Value::Float(f) => Ok(Value::Float(f.round())),
_ => bail!("round() requires a numeric argument"),
}
}
}
#[derive(Debug, Clone)]
struct CeilFunction;
impl CustomFunction for CeilFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#ceiling"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Float]
}
fn return_type(&self) -> ValueType {
ValueType::Float
}
fn documentation(&self) -> &str {
"Returns the ceiling of a number"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("ceil() requires exactly 1 argument");
}
match &args[0] {
Value::Integer(i) => Ok(Value::Integer(*i)),
Value::Float(f) => Ok(Value::Float(f.ceil())),
_ => bail!("ceil() requires a numeric argument"),
}
}
}
#[derive(Debug, Clone)]
struct FloorFunction;
impl CustomFunction for FloorFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#floor"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Float]
}
fn return_type(&self) -> ValueType {
ValueType::Float
}
fn documentation(&self) -> &str {
"Returns the floor of a number"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("floor() requires exactly 1 argument");
}
match &args[0] {
Value::Integer(i) => Ok(Value::Integer(*i)),
Value::Float(f) => Ok(Value::Float(f.floor())),
_ => bail!("floor() requires a numeric argument"),
}
}
}
#[derive(Debug, Clone)]
struct RandFunction;
impl CustomFunction for RandFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#random"
}
fn arity(&self) -> Option<usize> {
Some(0)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![]
}
fn return_type(&self) -> ValueType {
ValueType::Float
}
fn documentation(&self) -> &str {
"Returns a random number between 0 and 1"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if !args.is_empty() {
bail!("rand() takes no arguments");
}
Ok(Value::Float({
use scirs2_core::random::{rng, RngExt};
let mut random = rng();
random.random::<f64>()
}))
}
}
#[derive(Debug, Clone)]
struct NowFunction;
impl CustomFunction for NowFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#current-dateTime"
}
fn arity(&self) -> Option<usize> {
Some(0)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![]
}
fn return_type(&self) -> ValueType {
ValueType::DateTime
}
fn documentation(&self) -> &str {
"Returns the current date and time"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], context: &ExecutionContext) -> Result<Value> {
if !args.is_empty() {
bail!("now() takes no arguments");
}
Ok(Value::DateTime(context.query_time))
}
}
#[derive(Debug, Clone)]
struct YearFunction;
impl CustomFunction for YearFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#year-from-dateTime"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::DateTime]
}
fn return_type(&self) -> ValueType {
ValueType::Integer
}
fn documentation(&self) -> &str {
"Extracts the year from a dateTime"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("year() requires exactly 1 argument");
}
match &args[0] {
Value::DateTime(dt) => Ok(Value::Integer(dt.year() as i64)),
_ => bail!("year() requires a dateTime argument"),
}
}
}
#[derive(Debug, Clone)]
struct IsIriFunction;
impl CustomFunction for IsIriFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#isIRI"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Literal]
}
fn return_type(&self) -> ValueType {
ValueType::Boolean
}
fn documentation(&self) -> &str {
"Tests if a term is an IRI"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("isIRI() requires exactly 1 argument");
}
let is_iri = matches!(&args[0], Value::Iri(_));
Ok(Value::Boolean(is_iri))
}
}
#[derive(Debug, Clone)]
struct IsUriFunction;
impl CustomFunction for IsUriFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#isURI"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Literal]
}
fn return_type(&self) -> ValueType {
ValueType::Boolean
}
fn documentation(&self) -> &str {
"Alias for isIRI()"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], context: &ExecutionContext) -> Result<Value> {
IsIriFunction.execute(args, context)
}
}
#[derive(Debug, Clone)]
struct IsBlankFunction;
impl CustomFunction for IsBlankFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#isBlank"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Literal]
}
fn return_type(&self) -> ValueType {
ValueType::Boolean
}
fn documentation(&self) -> &str {
"Tests if a term is a blank node"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("isBlank() requires exactly 1 argument");
}
let is_blank = matches!(&args[0], Value::BlankNode(_));
Ok(Value::Boolean(is_blank))
}
}
#[derive(Debug, Clone)]
struct IsLiteralFunction;
impl CustomFunction for IsLiteralFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#isLiteral"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Literal]
}
fn return_type(&self) -> ValueType {
ValueType::Boolean
}
fn documentation(&self) -> &str {
"Tests if a term is a literal"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("isLiteral() requires exactly 1 argument");
}
let is_literal = matches!(
&args[0],
Value::Literal { .. }
| Value::String(_)
| Value::Integer(_)
| Value::Float(_)
| Value::Boolean(_)
);
Ok(Value::Boolean(is_literal))
}
}
#[derive(Debug, Clone)]
struct IsNumericFunction;
impl CustomFunction for IsNumericFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#isNumeric"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Literal]
}
fn return_type(&self) -> ValueType {
ValueType::Boolean
}
fn documentation(&self) -> &str {
"Tests if a term is numeric"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("isNumeric() requires exactly 1 argument");
}
let is_numeric = matches!(&args[0], Value::Integer(_) | Value::Float(_));
Ok(Value::Boolean(is_numeric))
}
}
#[derive(Debug, Clone)]
struct IfFunction;
impl CustomFunction for IfFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#if"
}
fn arity(&self) -> Option<usize> {
Some(3)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::Boolean, ValueType::Literal, ValueType::Literal]
}
fn return_type(&self) -> ValueType {
ValueType::Literal
}
fn documentation(&self) -> &str {
"Conditional expression"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 3 {
bail!("if() requires exactly 3 arguments");
}
let condition = match &args[0] {
Value::Boolean(b) => *b,
_ => bail!("if() first argument must be boolean"),
};
if condition {
Ok(args[1].clone())
} else {
Ok(args[2].clone())
}
}
}
#[derive(Debug, Clone)]
struct CoalesceFunction;
impl CustomFunction for CoalesceFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#coalesce"
}
fn arity(&self) -> Option<usize> {
None
} fn parameter_types(&self) -> Vec<ValueType> {
vec![]
}
fn return_type(&self) -> ValueType {
ValueType::Literal
}
fn documentation(&self) -> &str {
"Returns the first non-null argument"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.is_empty() {
bail!("coalesce() requires at least 1 argument");
}
for arg in args {
if !matches!(arg, Value::Null) {
return Ok(arg.clone());
}
}
Ok(Value::Null)
}
}
#[derive(Debug, Clone)]
struct RegexFunction;
impl CustomFunction for RegexFunction {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#matches"
}
fn arity(&self) -> Option<usize> {
None
} fn parameter_types(&self) -> Vec<ValueType> {
vec![]
}
fn return_type(&self) -> ValueType {
ValueType::Boolean
}
fn documentation(&self) -> &str {
"Tests if a string matches a regular expression"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 2 && args.len() != 3 {
bail!("regex() requires 2 or 3 arguments");
}
let text = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("regex() first argument must be a string"),
};
let pattern = match &args[1] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("regex() second argument must be a string"),
};
let _flags = if args.len() == 3 {
match &args[2] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("regex() third argument must be a string"),
}
} else {
""
};
match Regex::new(pattern) {
Ok(re) => Ok(Value::Boolean(re.is_match(text))),
Err(_) => bail!("Invalid regular expression: {}", pattern),
}
}
}
#[derive(Debug, Clone)]
struct Md5Function;
impl CustomFunction for Md5Function {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#md5"
}
fn arity(&self) -> Option<usize> {
Some(1)
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![ValueType::String]
}
fn return_type(&self) -> ValueType {
ValueType::String
}
fn documentation(&self) -> &str {
"Computes MD5 hash of a string"
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
if args.len() != 1 {
bail!("md5() requires exactly 1 argument");
}
let s = match &args[0] {
Value::String(s) => s,
Value::Literal { value, .. } => value,
_ => bail!("md5() requires a string argument"),
};
Ok(Value::String(format!("md5:{len}", len = s.len())))
}
}
#[derive(Debug, Clone)]
struct CountAggregate;
impl CustomAggregate for CountAggregate {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#count"
}
fn documentation(&self) -> &str {
"Counts the number of values"
}
fn init(&self) -> Box<dyn AggregateState> {
Box::new(CountState { count: 0 })
}
}
#[derive(Debug, Clone)]
struct CountState {
count: i64,
}
impl AggregateState for CountState {
fn add(&mut self, _value: &Value) -> Result<()> {
self.count += 1;
Ok(())
}
fn result(&self) -> Result<Value> {
Ok(Value::Integer(self.count))
}
fn reset(&mut self) {
self.count = 0;
}
fn clone_state(&self) -> Box<dyn AggregateState> {
Box::new(self.clone())
}
}
#[derive(Debug, Clone)]
struct SumAggregate;
impl CustomAggregate for SumAggregate {
fn name(&self) -> &str {
"http://www.w3.org/2005/xpath-functions#sum"
}
fn documentation(&self) -> &str {
"Sums numeric values"
}
fn init(&self) -> Box<dyn AggregateState> {
Box::new(SumState { sum: 0.0, count: 0 })
}
}
#[derive(Debug, Clone)]
struct SumState {
sum: f64,
count: usize,
}
impl AggregateState for SumState {
fn add(&mut self, value: &Value) -> Result<()> {
match value {
Value::Integer(i) => {
self.sum += *i as f64;
self.count += 1;
}
Value::Float(f) => {
self.sum += f;
self.count += 1;
}
_ => bail!("sum() can only aggregate numeric values"),
}
Ok(())
}
fn result(&self) -> Result<Value> {
if self.count == 0 {
Ok(Value::Integer(0))
} else {
Ok(Value::Float(self.sum))
}
}
fn reset(&mut self) {
self.sum = 0.0;
self.count = 0;
}
fn clone_state(&self) -> Box<dyn AggregateState> {
Box::new(self.clone())
}
}
macro_rules! stub_function {
($name:ident, $iri:expr_2021, $arity:expr_2021, $doc:expr_2021) => {
#[derive(Debug, Clone)]
struct $name;
impl CustomFunction for $name {
fn name(&self) -> &str {
$iri
}
fn arity(&self) -> Option<usize> {
$arity
}
fn parameter_types(&self) -> Vec<ValueType> {
vec![]
}
fn return_type(&self) -> ValueType {
ValueType::String
}
fn documentation(&self) -> &str {
$doc
}
fn clone_function(&self) -> Box<dyn CustomFunction> {
Box::new(self.clone())
}
fn execute(&self, _args: &[Value], _context: &ExecutionContext) -> Result<Value> {
bail!("Function {} not yet implemented", self.name())
}
}
};
}
#[derive(Debug, Clone)]
struct StubAggregateState;
impl AggregateState for StubAggregateState {
fn add(&mut self, _value: &Value) -> Result<()> {
Ok(())
}
fn result(&self) -> Result<Value> {
Ok(Value::Null)
}
fn reset(&mut self) {}
fn clone_state(&self) -> Box<dyn AggregateState> {
Box::new(StubAggregateState)
}
}
macro_rules! stub_aggregate {
($name:ident, $iri:expr_2021, $doc:expr_2021) => {
#[derive(Debug, Clone)]
struct $name;
impl CustomAggregate for $name {
fn name(&self) -> &str {
$iri
}
fn documentation(&self) -> &str {
$doc
}
fn init(&self) -> Box<dyn AggregateState> {
Box::new(StubAggregateState)
}
}
};
}
stub_function!(
MonthFunction,
"http://www.w3.org/2005/xpath-functions#month-from-dateTime",
Some(1),
"Extracts month from dateTime"
);
stub_function!(
DayFunction,
"http://www.w3.org/2005/xpath-functions#day-from-dateTime",
Some(1),
"Extracts day from dateTime"
);
stub_function!(
HoursFunction,
"http://www.w3.org/2005/xpath-functions#hours-from-dateTime",
Some(1),
"Extracts hours from dateTime"
);
stub_function!(
MinutesFunction,
"http://www.w3.org/2005/xpath-functions#minutes-from-dateTime",
Some(1),
"Extracts minutes from dateTime"
);
stub_function!(
SecondsFunction,
"http://www.w3.org/2005/xpath-functions#seconds-from-dateTime",
Some(1),
"Extracts seconds from dateTime"
);
stub_function!(
TimezoneFunction,
"http://www.w3.org/2005/xpath-functions#timezone-from-dateTime",
Some(1),
"Extracts timezone from dateTime"
);
stub_function!(
TzFunction,
"http://www.w3.org/2005/xpath-functions#tz",
Some(1),
"Returns timezone string"
);
stub_function!(
Sha1Function,
"http://www.w3.org/2005/xpath-functions#sha1",
Some(1),
"Computes SHA1 hash"
);
stub_function!(
Sha256Function,
"http://www.w3.org/2005/xpath-functions#sha256",
Some(1),
"Computes SHA256 hash"
);
stub_function!(
Sha384Function,
"http://www.w3.org/2005/xpath-functions#sha384",
Some(1),
"Computes SHA384 hash"
);
stub_function!(
Sha512Function,
"http://www.w3.org/2005/xpath-functions#sha512",
Some(1),
"Computes SHA512 hash"
);
stub_aggregate!(
AvgAggregate,
"http://www.w3.org/2005/xpath-functions#avg",
"Computes average of numeric values"
);
stub_aggregate!(
MinAggregate,
"http://www.w3.org/2005/xpath-functions#min",
"Finds minimum value"
);
stub_aggregate!(
MaxAggregate,
"http://www.w3.org/2005/xpath-functions#max",
"Finds maximum value"
);
stub_aggregate!(
GroupConcatAggregate,
"http://www.w3.org/2005/xpath-functions#group-concat",
"Concatenates group values"
);
stub_aggregate!(
SampleAggregate,
"http://www.w3.org/2005/xpath-functions#sample",
"Returns sample value from group"
);