rdf_fusion_functions/scalar/strings/
replace.rs

1use crate::scalar::dispatch::{
2    dispatch_quaternary_owned_typed_value, dispatch_ternary_owned_typed_value,
3};
4use crate::scalar::sparql_op_impl::{
5    ScalarSparqlOpImpl, create_typed_value_sparql_op_impl,
6};
7use crate::scalar::strings::regex::compile_pattern;
8use crate::scalar::{ScalarSparqlOp, ScalarSparqlOpSignature, SparqlOpArity};
9use rdf_fusion_encoding::typed_value::TypedValueEncoding;
10use rdf_fusion_extensions::functions::BuiltinName;
11use rdf_fusion_extensions::functions::FunctionName;
12use rdf_fusion_model::{
13    LanguageString, SimpleLiteral, SimpleLiteralRef, StringLiteralRef, ThinError,
14    TypedValue, TypedValueRef,
15};
16use std::borrow::Cow;
17
18/// Implementation of the SPARQL `regex` function (binary version).
19#[derive(Debug, Hash, PartialEq, Eq)]
20pub struct ReplaceSparqlOp;
21
22impl Default for ReplaceSparqlOp {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl ReplaceSparqlOp {
29    const NAME: FunctionName = FunctionName::Builtin(BuiltinName::Replace);
30
31    /// Creates a new [ReplaceSparqlOp].
32    pub fn new() -> Self {
33        Self {}
34    }
35}
36
37impl ScalarSparqlOp for ReplaceSparqlOp {
38    fn name(&self) -> &FunctionName {
39        &Self::NAME
40    }
41
42    fn signature(&self) -> ScalarSparqlOpSignature {
43        ScalarSparqlOpSignature::default_with_arity(SparqlOpArity::OneOf(vec![
44            SparqlOpArity::Fixed(3),
45            SparqlOpArity::Fixed(4),
46        ]))
47    }
48
49    fn typed_value_encoding_op(
50        &self,
51    ) -> Option<Box<dyn ScalarSparqlOpImpl<TypedValueEncoding>>> {
52        Some(create_typed_value_sparql_op_impl(|args| {
53            match args.args.len() {
54                3 => dispatch_ternary_owned_typed_value(
55                    &args.args[0],
56                    &args.args[1],
57                    &args.args[2],
58                    |arg0, arg1, arg2| evaluate_replace(arg0, arg1, arg2, None)?,
59                    |_, _, _| ThinError::expected(),
60                ),
61                4 => dispatch_quaternary_owned_typed_value(
62                    &args.args[0],
63                    &args.args[1],
64                    &args.args[2],
65                    &args.args[3],
66                    |arg0, arg1, arg2, arg3| {
67                        evaluate_replace(arg0, arg1, arg2, Some(arg3))?
68                    },
69                    |_, _, _, _| ThinError::expected(),
70                ),
71                _ => unreachable!("Invalid number of arguments"),
72            }
73        }))
74    }
75}
76
77fn evaluate_replace(
78    arg0: TypedValueRef<'_>,
79    arg1: TypedValueRef<'_>,
80    arg2: TypedValueRef<'_>,
81    arg3: Option<TypedValueRef<'_>>,
82) -> Result<Result<TypedValue, ThinError>, ThinError> {
83    let arg0 = StringLiteralRef::try_from(arg0)?;
84    let arg1 = SimpleLiteralRef::try_from(arg1)?;
85    let arg2 = SimpleLiteralRef::try_from(arg2)?;
86    let arg3 = arg3.map(SimpleLiteralRef::try_from).transpose()?;
87
88    let regex = compile_pattern(arg1.value, arg3.map(|lit| lit.value))?;
89
90    let result = match regex.replace_all(arg0.0, arg2.value) {
91        Cow::Owned(replaced) => replaced,
92        Cow::Borrowed(_) => arg0.0.to_owned(),
93    };
94
95    Ok(Ok(match arg0.1 {
96        None => TypedValue::SimpleLiteral(SimpleLiteral { value: result }),
97        Some(language) => TypedValue::LanguageStringLiteral(LanguageString {
98            value: result,
99            language: language.to_owned(),
100        }),
101    }))
102}